refactor(segments): migrate 19 languages to a generic engine

Most language segments were ~20-30 line Go files that only declared
file extensions, an executable name, and a version regex before
delegating to the shared Language base. Every new simple language
(e.g. the recent GCC PR) meant another one-off struct, test file,
and registration to review and maintain.

Replace fortran, ruby, clojure, crystal, elixir, julia, kotlin, lua,
nim, ocaml, perl, php, r, rust, swift, v, vala, zig, and dart with a
single ConfiguredLanguage engine driven by a built-in preset table.
Public config surface (segment type strings, all existing options)
is unchanged; this is purely an internal consolidation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Entire-Checkpoint: 6d449f3da816
This commit is contained in:
Jan De Dobbeleer
2026-07-17 13:47:33 +02:00
committed by Jan De Dobbeleer
co-authored by Claude Sonnet 5
parent 5c27aaadf0
commit 59969e90b5
44 changed files with 1347 additions and 1567 deletions
+20 -38
View File
@@ -47,22 +47,18 @@ func init() {
gob.Register(&segments.CfTarget{})
gob.Register(&segments.Claude{})
gob.Register(&segments.ClaudeData{})
gob.Register(&segments.Clojure{})
gob.Register(&segments.Cmake{})
gob.Register(&segments.ConfiguredLanguage{})
gob.Register(&segments.Connection{})
gob.Register(&segments.Crystal{})
gob.Register(&segments.Dart{})
gob.Register(&segments.Deno{})
gob.Register(&segments.Docker{})
gob.Register(&segments.Dotnet{})
gob.Register(&segments.Dvc{})
gob.Register(&segments.DvcStatus{})
gob.Register(&segments.Elixir{})
gob.Register(&segments.Executiontime{})
gob.Register(&segments.Status{})
gob.Register(&segments.Firebase{})
gob.Register(&segments.Flutter{})
gob.Register(&segments.Fortran{})
gob.Register(&segments.Fossil{})
gob.Register(&segments.FossilStatus{})
gob.Register(&segments.Gcp{})
@@ -81,11 +77,8 @@ func init() {
gob.Register(&segments.HTTP{})
gob.Register(&segments.Jujutsu{})
gob.Register(&segments.JujutsuStatus{})
gob.Register(&segments.Julia{})
gob.Register(&segments.Kotlin{})
gob.Register(&segments.Kubectl{})
gob.Register(&segments.LastFM{})
gob.Register(&segments.Lua{})
gob.Register(&segments.Mercurial{})
gob.Register(&segments.MercurialStatus{})
gob.Register(&segments.Mojo{})
@@ -94,18 +87,14 @@ func init() {
gob.Register(&segments.Nbgv{})
gob.Register(&segments.Nightscout{})
gob.Register(&segments.NixShell{})
gob.Register(&segments.Nim{})
gob.Register(&segments.Node{})
gob.Register(&segments.Npm{})
gob.Register(&segments.Nx{})
gob.Register(&segments.OCaml{})
gob.Register(&segments.OrthodoxCal{})
gob.Register(&segments.Os{})
gob.Register(&segments.Owm{})
gob.Register(&segments.Path{})
gob.Register(&segments.Folders{})
gob.Register(&segments.Perl{})
gob.Register(&segments.Php{})
gob.Register(&segments.Plastic{})
gob.Register(&segments.PlasticStatus{})
gob.Register(&segments.Pnpm{})
@@ -114,12 +103,9 @@ func init() {
gob.Register(&segments.Python{})
gob.Register(&segments.Quasar{})
gob.Register(&segments.Package{})
gob.Register(&segments.R{})
gob.Register(&segments.Ramadan{})
gob.Register(&segments.React{})
gob.Register(&segments.Root{})
gob.Register(&segments.Ruby{})
gob.Register(&segments.Rust{})
gob.Register(&segments.Sapling{})
gob.Register(&segments.SaplingStatus{})
gob.Register(&segments.Session{})
@@ -131,7 +117,6 @@ func init() {
gob.Register(&segments.Svelte{})
gob.Register(&segments.Svn{})
gob.Register(&segments.SvnStatus{})
gob.Register(&segments.Swift{})
gob.Register(&segments.SystemInfo{})
gob.Register(&segments.TalosCTL{})
gob.Register(&segments.Taskwarrior{})
@@ -146,8 +131,6 @@ func init() {
gob.Register(&segments.Unity{})
gob.Register(&segments.Upgrade{})
gob.Register(&segments.UpgradeCache{})
gob.Register(&segments.V{})
gob.Register(&segments.Vala{})
gob.Register(&segments.VIMode{})
gob.Register(&segments.Wakatime{})
gob.Register(&segments.WinGet{})
@@ -157,7 +140,6 @@ func init() {
gob.Register(&segments.XMake{})
gob.Register(&segments.Yarn{})
gob.Register(&segments.Ytm{})
gob.Register(&segments.Zig{})
gob.Register(&segments.Segment{})
}
@@ -427,23 +409,23 @@ var Segments = map[SegmentType]func() SegmentWriter{
CF: func() SegmentWriter { return &segments.Cf{} },
CFTARGET: func() SegmentWriter { return &segments.CfTarget{} },
CLAUDE: func() SegmentWriter { return &segments.Claude{} },
CLOJURE: func() SegmentWriter { return &segments.Clojure{} },
CLOJURE: func() SegmentWriter { return segments.NewLanguage(string(CLOJURE)) },
CMAKE: func() SegmentWriter { return &segments.Cmake{} },
CONNECTION: func() SegmentWriter { return &segments.Connection{} },
COPILOT: func() SegmentWriter { return &segments.Copilot{} },
COPILOTCLI: func() SegmentWriter { return &segments.CopilotCLI{} },
CRYSTAL: func() SegmentWriter { return &segments.Crystal{} },
DART: func() SegmentWriter { return &segments.Dart{} },
CRYSTAL: func() SegmentWriter { return segments.NewLanguage(string(CRYSTAL)) },
DART: func() SegmentWriter { return segments.NewLanguage(string(DART)) },
DENO: func() SegmentWriter { return &segments.Deno{} },
DOCKER: func() SegmentWriter { return &segments.Docker{} },
DOTNET: func() SegmentWriter { return &segments.Dotnet{} },
DVC: func() SegmentWriter { return &segments.Dvc{} },
ELIXIR: func() SegmentWriter { return &segments.Elixir{} },
ELIXIR: func() SegmentWriter { return segments.NewLanguage(string(ELIXIR)) },
EXECUTIONTIME: func() SegmentWriter { return &segments.Executiontime{} },
EXIT: func() SegmentWriter { return &segments.Status{} },
FIREBASE: func() SegmentWriter { return &segments.Firebase{} },
FLUTTER: func() SegmentWriter { return &segments.Flutter{} },
FORTRAN: func() SegmentWriter { return &segments.Fortran{} },
FORTRAN: func() SegmentWriter { return segments.NewLanguage(string(FORTRAN)) },
FOSSIL: func() SegmentWriter { return &segments.Fossil{} },
GCP: func() SegmentWriter { return &segments.Gcp{} },
GIT: func() SegmentWriter { return &segments.Git{} },
@@ -456,11 +438,11 @@ var Segments = map[SegmentType]func() SegmentWriter{
JAVA: func() SegmentWriter { return &segments.Java{} },
HTTP: func() SegmentWriter { return &segments.HTTP{} },
JUJUTSU: func() SegmentWriter { return &segments.Jujutsu{} },
JULIA: func() SegmentWriter { return &segments.Julia{} },
KOTLIN: func() SegmentWriter { return &segments.Kotlin{} },
JULIA: func() SegmentWriter { return segments.NewLanguage(string(JULIA)) },
KOTLIN: func() SegmentWriter { return segments.NewLanguage(string(KOTLIN)) },
KUBECTL: func() SegmentWriter { return &segments.Kubectl{} },
LASTFM: func() SegmentWriter { return &segments.LastFM{} },
LUA: func() SegmentWriter { return &segments.Lua{} },
LUA: func() SegmentWriter { return segments.NewLanguage(string(LUA)) },
MERCURIAL: func() SegmentWriter { return &segments.Mercurial{} },
MOJO: func() SegmentWriter { return &segments.Mojo{} },
MVN: func() SegmentWriter { return &segments.Mvn{} },
@@ -468,29 +450,29 @@ var Segments = map[SegmentType]func() SegmentWriter{
NBGV: func() SegmentWriter { return &segments.Nbgv{} },
NIGHTSCOUT: func() SegmentWriter { return &segments.Nightscout{} },
NIXSHELL: func() SegmentWriter { return &segments.NixShell{} },
NIM: func() SegmentWriter { return &segments.Nim{} },
NIM: func() SegmentWriter { return segments.NewLanguage(string(NIM)) },
NODE: func() SegmentWriter { return &segments.Node{} },
NPM: func() SegmentWriter { return &segments.Npm{} },
NX: func() SegmentWriter { return &segments.Nx{} },
OCAML: func() SegmentWriter { return &segments.OCaml{} },
OCAML: func() SegmentWriter { return segments.NewLanguage(string(OCAML)) },
ORTHODOXCAL: func() SegmentWriter { return &segments.OrthodoxCal{} },
OS: func() SegmentWriter { return &segments.Os{} },
OWM: func() SegmentWriter { return &segments.Owm{} },
PATH: func() SegmentWriter { return &segments.Path{} },
PERL: func() SegmentWriter { return &segments.Perl{} },
PHP: func() SegmentWriter { return &segments.Php{} },
PERL: func() SegmentWriter { return segments.NewLanguage(string(PERL)) },
PHP: func() SegmentWriter { return segments.NewLanguage(string(PHP)) },
PLASTIC: func() SegmentWriter { return &segments.Plastic{} },
PNPM: func() SegmentWriter { return &segments.Pnpm{} },
PROJECT: func() SegmentWriter { return &segments.Project{} },
PULUMI: func() SegmentWriter { return &segments.Pulumi{} },
PYTHON: func() SegmentWriter { return &segments.Python{} },
QUASAR: func() SegmentWriter { return &segments.Quasar{} },
R: func() SegmentWriter { return &segments.R{} },
R: func() SegmentWriter { return segments.NewLanguage(string(R)) },
RAMADAN: func() SegmentWriter { return &segments.Ramadan{} },
REACT: func() SegmentWriter { return &segments.React{} },
ROOT: func() SegmentWriter { return &segments.Root{} },
RUBY: func() SegmentWriter { return &segments.Ruby{} },
RUST: func() SegmentWriter { return &segments.Rust{} },
RUBY: func() SegmentWriter { return segments.NewLanguage(string(RUBY)) },
RUST: func() SegmentWriter { return segments.NewLanguage(string(RUST)) },
SAPLING: func() SegmentWriter { return &segments.Sapling{} },
SESSION: func() SegmentWriter { return &segments.Session{} },
SHELL: func() SegmentWriter { return &segments.Shell{} },
@@ -500,7 +482,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
STRAVA: func() SegmentWriter { return &segments.Strava{} },
SVELTE: func() SegmentWriter { return &segments.Svelte{} },
SVN: func() SegmentWriter { return &segments.Svn{} },
SWIFT: func() SegmentWriter { return &segments.Swift{} },
SWIFT: func() SegmentWriter { return segments.NewLanguage(string(SWIFT)) },
SYSTEMINFO: func() SegmentWriter { return &segments.SystemInfo{} },
TALOSCTL: func() SegmentWriter { return &segments.TalosCTL{} },
TASKWARRIOR: func() SegmentWriter { return &segments.Taskwarrior{} },
@@ -514,8 +496,8 @@ var Segments = map[SegmentType]func() SegmentWriter{
UNO: func() SegmentWriter { return &segments.Uno{} },
UNITY: func() SegmentWriter { return &segments.Unity{} },
UPGRADE: func() SegmentWriter { return &segments.Upgrade{} },
V: func() SegmentWriter { return &segments.V{} },
VALA: func() SegmentWriter { return &segments.Vala{} },
V: func() SegmentWriter { return segments.NewLanguage(string(V)) },
VALA: func() SegmentWriter { return segments.NewLanguage(string(VALA)) },
VIMODE: func() SegmentWriter { return &segments.VIMode{} },
WAKATIME: func() SegmentWriter { return &segments.Wakatime{} },
WINGET: func() SegmentWriter { return &segments.WinGet{} },
@@ -524,7 +506,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
XMAKE: func() SegmentWriter { return &segments.XMake{} },
YARN: func() SegmentWriter { return &segments.Yarn{} },
YTM: func() SegmentWriter { return &segments.Ytm{} },
ZIG: func() SegmentWriter { return &segments.Zig{} },
ZIG: func() SegmentWriter { return segments.NewLanguage(string(ZIG)) },
ZVM: func() SegmentWriter { return &segments.Zvm{} },
}
-48
View File
@@ -1,48 +0,0 @@
package segments
type Clojure struct {
Language
}
func (c *Clojure) Template() string {
return languageTemplate
}
func (c *Clojure) Enabled() bool {
c.init()
return c.Language.Enabled()
}
const clojureToolName = "clojure"
const leinToolName = "lein"
func (c *Clojure) init() {
options := c.options.StringArray(Tooling, []string{})
if len(options) == 0 {
c.defaultTooling = []string{clojureToolName, leinToolName}
}
c.extensions = []string{
"project.clj",
"deps.edn",
"build.boot",
"bb.edn",
"*.clj",
"*.cljc",
"*.cljs",
}
c.tooling = map[string]*cmd{
clojureToolName: {
executable: clojureToolName,
args: []string{versionFlagArg},
regex: `Clojure CLI version (?P<version>(?P<major>[0-9]+)\.(?P<minor>[0-9]+)\.(?P<patch>[0-9]+)(?:\.(?P<build>[0-9]+))?)`,
},
leinToolName: {
executable: leinToolName,
args: []string{versionFlagArg},
regex: `Leiningen (?P<version>(?P<major>[0-9]+)\.(?P<minor>[0-9]+)\.(?P<patch>[0-9]+))`,
},
}
}
-47
View File
@@ -1,47 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestClojure(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
Cmd string
}{
{
Case: "Clojure CLI 1.11.1.1113",
ExpectedString: "1.11.1.1113",
Version: "Clojure CLI version 1.11.1.1113",
Cmd: "clojure",
},
{
Case: "Leiningen 2.9.8",
ExpectedString: "2.9.8",
Version: "Leiningen 2.9.8 on Java 11.0.11 OpenJDK 64-Bit Server VM",
Cmd: "lein",
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: tc.Cmd,
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.clj",
}
env, props := getMockedLanguageEnv(params)
props[LanguageExtensions] = []string{params.extension}
if tc.Cmd != "clojure" {
env.On("HasCommand", "clojure").Return(false)
}
c := &Clojure{}
c.Init(props, env)
assert.True(t, c.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, c.Template(), c), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
-26
View File
@@ -1,26 +0,0 @@
package segments
type Crystal struct {
Language
}
func (c *Crystal) Template() string {
return languageTemplate
}
const crystalToolName = "crystal"
func (c *Crystal) Enabled() bool {
c.extensions = []string{"*.cr", "shard.yml"}
c.tooling = map[string]*cmd{
crystalToolName: {
executable: crystalToolName,
args: []string{versionFlagArg},
regex: `Crystal ` + versionRegex,
},
}
c.defaultTooling = []string{crystalToolName}
c.versionURLTemplate = "https://github.com/crystal-lang/crystal/releases/tag/{{ .Full }}"
return c.Language.Enabled()
}
-31
View File
@@ -1,31 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCrystal(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{Case: "Crystal 1.0.0", ExpectedString: "1.0.0", Version: "Crystal 1.0.0 (2021-03-22)"},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "crystal",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.cr",
}
env, props := getMockedLanguageEnv(params)
c := &Crystal{}
c.Init(props, env)
assert.True(t, c.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, c.Template(), c), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
-35
View File
@@ -1,35 +0,0 @@
package segments
var (
dartExtensions = []string{"*.dart", "pubspec.yaml", "pubspec.yml", "pubspec.lock"}
dartFolders = []string{".dart_tool"}
)
type Dart struct {
Language
}
func (d *Dart) Template() string {
return languageTemplate
}
func (d *Dart) Enabled() bool {
d.extensions = dartExtensions
d.folders = dartFolders
d.tooling = map[string]*cmd{
fvmToolName: {
executable: fvmToolName,
args: []string{dartToolName, versionFlagArg},
regex: `Dart SDK version: ` + versionRegex,
},
dartToolName: {
executable: dartToolName,
args: []string{versionFlagArg},
regex: `Dart SDK version: ` + versionRegex,
},
}
d.defaultTooling = []string{fvmToolName, dartToolName}
d.versionURLTemplate = "https://dart.dev/guides/language/evolution#dart-{{ .Major }}{{ .Minor }}"
return d.Language.Enabled()
}
-35
View File
@@ -1,35 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDart(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{Case: "Dart 2.12.4", ExpectedString: "2.12.4", Version: "Dart SDK version: 2.12.4 (stable) (Thu Apr 15 12:26:53 2021 +0200) on \"macos_x64\""},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "dart",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.dart",
}
env, props := getMockedLanguageEnv(params)
env.On("HasCommand", "fvm").Return(false)
d := &Dart{}
d.Init(props, env)
assert.True(t, d.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, d.Template(), d), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
-31
View File
@@ -1,31 +0,0 @@
package segments
type Elixir struct {
Language
}
func (e *Elixir) Template() string {
return languageTemplate
}
const elixirToolName = "elixir"
func (e *Elixir) Enabled() bool {
e.extensions = []string{"*.ex", "*.exs"}
e.tooling = map[string]*cmd{
asdfToolName: {
executable: asdfToolName,
args: []string{"current", elixirToolName},
regex: `elixir\s+` + versionRegex + `[^\s]*\s+`,
},
elixirToolName: {
executable: elixirToolName,
args: []string{versionFlagArg},
regex: `Elixir ` + versionRegex,
},
}
e.defaultTooling = []string{asdfToolName, elixirToolName}
e.versionURLTemplate = "https://github.com/elixir-lang/elixir/releases/tag/v{{ .Full }}"
return e.Language.Enabled()
}
-62
View File
@@ -1,62 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/stretchr/testify/assert"
)
func TestElixir(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
ElixirVersionOutput string
AsdfVersionOutput string
HasAsdf bool
AsdfExitCode int
}{
{
Case: "Version without asdf",
ExpectedString: "1.14.2",
ElixirVersionOutput: "Erlang/OTP 25 [erts-13.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]\n\nElixir 1.14.2 (compiled with Erlang/OTP 25)",
},
{
Case: "Version with asdf",
ExpectedString: "1.14.2",
HasAsdf: true,
AsdfVersionOutput: "elixir 1.14.2-otp-25 /path/to/.tool-versions",
ElixirVersionOutput: "Should not be used",
},
{
Case: "Version with asdf not set: should fall back to elixir --version",
ExpectedString: "1.14.2",
HasAsdf: true,
AsdfVersionOutput: "elixir ______ No version is set. Run \"asdf <global|shell|local> elixir <version>\"",
AsdfExitCode: 126,
ElixirVersionOutput: "Erlang/OTP 25 [erts-13.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]\n\nElixir 1.14.2 (compiled with Erlang/OTP 25)",
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "elixir",
versionParam: "--version",
versionOutput: tc.ElixirVersionOutput,
extension: "*.ex",
}
env, props := getMockedLanguageEnv(params)
env.On("HasCommand", "asdf").Return(tc.HasAsdf)
var asdfErr error
if tc.AsdfExitCode != 0 {
asdfErr = &runtime.CommandError{ExitCode: tc.AsdfExitCode}
}
env.On("RunCommandWithEnv", "asdf", []string(nil), []string{"current", "elixir"}).Return(tc.AsdfVersionOutput, asdfErr)
r := &Elixir{}
r.Init(props, env)
assert.True(t, r.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, r.Template(), r), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
+2 -2
View File
@@ -11,8 +11,8 @@ func (f *Flutter) Template() string {
const flutterToolName = "flutter"
func (f *Flutter) Enabled() bool {
f.extensions = dartExtensions
f.folders = dartFolders
f.extensions = []string{"*.dart", pubspecFileName, "pubspec.yml", "pubspec.lock"}
f.folders = []string{".dart_tool"}
f.tooling = map[string]*cmd{
fvmToolName: {
executable: fvmToolName,
-33
View File
@@ -1,33 +0,0 @@
package segments
type Fortran struct {
Language
}
func (f *Fortran) Template() string {
return languageTemplate
}
const gfortranToolName = "gfortran"
func (f *Fortran) Enabled() bool {
f.extensions = []string{
"*.f", "*.for", "*.fpp",
"*.f77", "*.f90", "*.f95",
"*.f03", "*.f08",
"*.F", "*.FOR", "*.FPP",
"*.F77", "*.F90", "*.F95",
"*.F03", "*.F08",
"fpm.toml",
}
f.tooling = map[string]*cmd{
gfortranToolName: {
executable: gfortranToolName,
args: []string{versionFlagArg},
regex: `GNU Fortran \(.*\) ` + versionRegex,
},
}
f.defaultTooling = []string{gfortranToolName}
return f.Language.Enabled()
}
-46
View File
@@ -1,46 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFortran(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{
Case: "GNU Fortran 10.2.1 Debian",
ExpectedString: "10.2.1",
Version: `GNU Fortran (Debian 10.2.1-6) 10.2.1 20210110
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.`,
},
{
Case: "GNU Fortran 11.4.0 Ubuntu",
ExpectedString: "11.4.0",
Version: `GNU Fortran (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.`,
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "gfortran",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.f",
}
env, props := getMockedLanguageEnv(params)
f := &Fortran{}
f.Init(props, env)
assert.True(t, f.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, f.Template(), f), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
-24
View File
@@ -1,24 +0,0 @@
package segments
type Julia struct {
Language
}
func (j *Julia) Template() string {
return languageTemplate
}
func (j *Julia) Enabled() bool {
j.extensions = []string{"*.jl"}
j.tooling = map[string]*cmd{
juliaToolName: {
executable: juliaToolName,
args: []string{versionFlagArg},
regex: `julia version ` + versionRegex,
},
}
j.defaultTooling = []string{juliaToolName}
j.versionURLTemplate = "https://github.com/JuliaLang/julia/releases/tag/v{{ .Full }}"
return j.Language.Enabled()
}
-32
View File
@@ -1,32 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestJulia(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{Case: "Julia 1.6.0", ExpectedString: "1.6.0", Version: "julia version 1.6.0"},
{Case: "Julia 1.6.1", ExpectedString: "1.6.1", Version: "julia version 1.6.1"},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "julia",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.jl",
}
env, props := getMockedLanguageEnv(params)
j := &Julia{}
j.Init(props, env)
assert.True(t, j.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, j.Template(), j), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
-26
View File
@@ -1,26 +0,0 @@
package segments
type Kotlin struct {
Language
}
func (k *Kotlin) Template() string {
return languageTemplate
}
const kotlinToolName = "kotlin"
func (k *Kotlin) Enabled() bool {
k.extensions = []string{"*.kt", "*.kts", "*.ktm"}
k.tooling = map[string]*cmd{
kotlinToolName: {
executable: kotlinToolName,
args: []string{versionFlagShortArg},
regex: `Kotlin version ` + versionRegex,
},
}
k.defaultTooling = []string{kotlinToolName}
k.versionURLTemplate = "https://github.com/JetBrains/kotlin/releases/tag/v{{ .Full }}"
return k.Language.Enabled()
}
-32
View File
@@ -1,32 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestKotlin(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{Case: "Kotlin 1.6.10", ExpectedString: "1.6.10", Version: "Kotlin version 1.6.10-release-923 (JRE 17.0.2+0)"},
{Case: "Kotlin 1.6.0", ExpectedString: "1.6.0", Version: "Kotlin version 1.6.0-release-915 (JRE 17.0.2+0)"},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "kotlin",
versionParam: "-version",
versionOutput: tc.Version,
extension: "*.kt",
}
env, props := getMockedLanguageEnv(params)
k := &Kotlin{}
k.Init(props, env)
assert.True(t, k.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, k.Template(), k), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
+18 -2
View File
@@ -29,7 +29,8 @@ const (
versionRegexPrefixed = `(?:(?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+).(?P<patch>[0-9]+))))`
versionRegexSemver = `(?:(?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+).(?P<patch>[0-9]+)(-(?P<prerelease>[a-z]+).(?P<buildmetadata>[0-9]+))?)))`
fileName = "package.json"
fileName = "package.json"
pubspecFileName = "pubspec.yaml"
asdfToolName = "asdf"
bunToolName = "bun"
@@ -134,8 +135,14 @@ const (
LanguageExtensions options.Option = "extensions"
// LanguageFolders the list of folders to validate
LanguageFolders options.Option = "folders"
// LanguageProjectFiles the list of project files to validate
LanguageProjectFiles options.Option = "project_files"
// Tooling allows enabling additional version fetching tools
Tooling options.Option = "tooling"
// Tools defines custom tools (executable, args, regex) for a configured language
Tools options.Option = "tools"
// LanguageName identifies a configured language segment; used as its cache key and preset lookup key
LanguageName options.Option = "name"
)
func (l *Language) getName() string {
@@ -145,10 +152,13 @@ func (l *Language) getName() string {
}
func (l *Language) Enabled() bool {
l.name = l.getName()
if l.name == "" {
l.name = l.getName()
}
// override default extensions if needed
l.extensions = l.options.StringArray(LanguageExtensions, l.extensions)
l.folders = l.options.StringArray(LanguageFolders, l.folders)
l.projectFiles = l.options.StringArray(LanguageProjectFiles, l.projectFiles)
inHomeDir := func() bool {
return l.env.Pwd() == l.env.Home()
}
@@ -243,6 +253,12 @@ func (l *Language) hasProjectFiles() bool {
return false
}
// InProjectDir reports whether the working directory is within a project
// matched by one of the segment's projectFiles.
func (l *Language) InProjectDir() bool {
return l.projectRoot != nil
}
func (l *Language) hasLanguageFolders() bool {
return slices.ContainsFunc(l.folders, l.env.HasFolder)
}
+456
View File
@@ -0,0 +1,456 @@
package segments
import (
"fmt"
"github.com/jandedobbeleer/oh-my-posh/src/segments/options"
)
// languageDefinition is a built-in preset for ConfiguredLanguage. Each entry mirrors what a
// dedicated language segment used to hardcode: which files trigger it, which executables can
// report its version, and how to parse that output.
type languageDefinition struct {
tooling map[string]*cmd
sanitizeVersion func(string) string
versionURLTemplate string
extensions []string
projectFiles []string
folders []string
defaultTooling []string
}
const (
gfortranToolName = "gfortran"
rbenvToolName = "rbenv"
rvmPromptToolName = "rvm-prompt"
chrubyToolName = "chruby"
rubyToolName = "ruby"
clojureToolName = "clojure"
leinToolName = "lein"
crystalToolName = "crystal"
elixirToolName = "elixir"
kotlinToolName = "kotlin"
luaToolName = "lua"
luajitToolName = "luajit"
nimToolName = "nim"
ocamlToolName = "ocaml"
perlToolName = "perl"
rscriptToolName = "Rscript"
rExeToolName = "R.exe"
rustcToolName = "rustc"
swiftToolName = "swift"
valaToolName = "vala"
zigToolName = "zig"
)
// languageDefinitions holds the built-in presets, keyed by the same name a migrated segment's
// SegmentType string used (e.g. "fortran"). ConfiguredLanguage looks itself up here by name;
// a name with no entry (the public "language" segment type) relies entirely on the `tools` option.
var languageDefinitions = map[string]languageDefinition{
"fortran": {
extensions: []string{
"*.f", "*.for", "*.fpp",
"*.f77", "*.f90", "*.f95",
"*.f03", "*.f08",
"*.F", "*.FOR", "*.FPP",
"*.F77", "*.F90", "*.F95",
"*.F03", "*.F08",
"fpm.toml",
},
tooling: map[string]*cmd{
gfortranToolName: {
executable: gfortranToolName,
args: []string{versionFlagArg},
regex: `GNU Fortran \(.*\) ` + versionRegex,
},
},
defaultTooling: []string{gfortranToolName},
},
rubyToolName: {
extensions: []string{"*.rb", "Rakefile", "Gemfile"},
tooling: map[string]*cmd{
rbenvToolName: {
executable: rbenvToolName,
args: []string{"version-name"},
regex: `(?P<version>.+)`,
},
rvmPromptToolName: {
executable: rvmPromptToolName,
args: []string{"i", "v", "g"},
regex: `(?P<version>.+)`,
},
chrubyToolName: {
executable: chrubyToolName,
args: []string(nil),
regex: `\* (?P<version>.+)\n`,
},
asdfToolName: {
executable: asdfToolName,
args: []string{"current", rubyToolName},
regex: `ruby\s+(?P<version>[^\s]+)\s+`,
},
rubyToolName: {
executable: rubyToolName,
args: []string{versionFlagArg},
regex: `ruby\s+(?P<version>[^\s]+)\s+`,
},
},
defaultTooling: []string{rbenvToolName, rvmPromptToolName, chrubyToolName, asdfToolName, rubyToolName},
// asdf reports "______" when no version is set for the tool; that isn't a version, it's an absence of one.
sanitizeVersion: func(version string) string {
if version == "______" {
return ""
}
return version
},
},
"clojure": {
extensions: []string{
"project.clj",
"deps.edn",
"build.boot",
"bb.edn",
"*.clj",
"*.cljc",
"*.cljs",
},
tooling: map[string]*cmd{
clojureToolName: {
executable: clojureToolName,
args: []string{versionFlagArg},
regex: `Clojure CLI version (?P<version>(?P<major>[0-9]+)\.(?P<minor>[0-9]+)\.(?P<patch>[0-9]+)(?:\.(?P<build>[0-9]+))?)`,
},
leinToolName: {
executable: leinToolName,
args: []string{versionFlagArg},
regex: `Leiningen (?P<version>(?P<major>[0-9]+)\.(?P<minor>[0-9]+)\.(?P<patch>[0-9]+))`,
},
},
defaultTooling: []string{clojureToolName, leinToolName},
},
"crystal": {
extensions: []string{"*.cr", "shard.yml"},
tooling: map[string]*cmd{
crystalToolName: {
executable: crystalToolName,
args: []string{versionFlagArg},
regex: `Crystal ` + versionRegex,
},
},
defaultTooling: []string{crystalToolName},
versionURLTemplate: "https://github.com/crystal-lang/crystal/releases/tag/{{ .Full }}",
},
"elixir": {
extensions: []string{"*.ex", "*.exs"},
tooling: map[string]*cmd{
asdfToolName: {
executable: asdfToolName,
args: []string{"current", elixirToolName},
regex: `elixir\s+` + versionRegex + `[^\s]*\s+`,
},
elixirToolName: {
executable: elixirToolName,
args: []string{versionFlagArg},
regex: `Elixir ` + versionRegex,
},
},
defaultTooling: []string{asdfToolName, elixirToolName},
versionURLTemplate: "https://github.com/elixir-lang/elixir/releases/tag/v{{ .Full }}",
},
"julia": {
extensions: []string{"*.jl"},
tooling: map[string]*cmd{
juliaToolName: {
executable: juliaToolName,
args: []string{versionFlagArg},
regex: `julia version ` + versionRegex,
},
},
defaultTooling: []string{juliaToolName},
versionURLTemplate: "https://github.com/JuliaLang/julia/releases/tag/v{{ .Full }}",
},
"kotlin": {
extensions: []string{"*.kt", "*.kts", "*.ktm"},
tooling: map[string]*cmd{
kotlinToolName: {
executable: kotlinToolName,
args: []string{versionFlagShortArg},
regex: `Kotlin version ` + versionRegex,
},
},
defaultTooling: []string{kotlinToolName},
versionURLTemplate: "https://github.com/JetBrains/kotlin/releases/tag/v{{ .Full }}",
},
"lua": {
extensions: []string{"*.lua", "*.rockspec"},
folders: []string{"lua"},
tooling: map[string]*cmd{
luaToolName: {
executable: luaToolName,
args: []string{"-v"},
regex: `Lua (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+)(.(?P<patch>[0-9]+))?))`,
versionURLTemplate: "https://www.lua.org/manual/{{ .Major }}.{{ .Minor }}/readme.html#changes",
},
luajitToolName: {
executable: luajitToolName,
args: []string{"-v"},
regex: `LuaJIT (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+)(.(?P<patch>[0-9]+))?))`,
versionURLTemplate: "https://github.com/LuaJIT/LuaJIT/tree/v{{ .Major}}.{{ .Minor}}",
},
},
defaultTooling: []string{luaToolName, luajitToolName},
},
"nim": {
extensions: []string{"*.nim", "*.nims"},
tooling: map[string]*cmd{
nimToolName: {
executable: nimToolName,
args: []string{versionFlagArg},
regex: `Nim Compiler Version (?P<version>(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+))`,
},
},
defaultTooling: []string{nimToolName},
},
"ocaml": {
extensions: []string{"*.ml", "*.mli", "dune", "dune-project", "dune-workspace"},
tooling: map[string]*cmd{
ocamlToolName: {
executable: ocamlToolName,
args: []string{versionFlagShortArg},
regex: `The OCaml toplevel, version (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+).(?P<patch>[0-9]+))(-(?P<prerelease>[a-z]+))?)`,
},
},
defaultTooling: []string{ocamlToolName},
},
"perl": {
extensions: []string{".perl-version", "*.pl", "*.pm", "*.t"},
tooling: map[string]*cmd{
perlToolName: {
executable: perlToolName,
args: []string{versionFlagShortArg},
regex: `This is perl.*v(?P<version>(?P<major>[0-9]+)(?:\.(?P<minor>[0-9]+))(?:\.(?P<patch>[0-9]+))?).* built for .+`,
},
},
defaultTooling: []string{perlToolName},
},
"php": {
extensions: []string{"*.php", "composer.json", "composer.lock", ".php-version", "blade.php"},
tooling: map[string]*cmd{
phpToolName: {
executable: phpToolName,
args: []string{versionFlagArg},
regex: `(?:PHP (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+).(?P<patch>[0-9]+))))`,
},
},
defaultTooling: []string{phpToolName},
versionURLTemplate: "https://www.php.net/ChangeLog-{{ .Major }}.php#PHP_{{ .Major }}_{{ .Minor }}",
},
"r": {
extensions: []string{"*.R", "*.Rmd", "*.Rsx", "*.Rda", "*.Rd", "*.Rproj", ".Rproj.user"},
tooling: map[string]*cmd{
rscriptToolName: {
executable: rscriptToolName,
args: []string{versionFlagArg},
regex: `version ` + versionRegex,
},
"R": {
executable: "R",
args: []string{versionFlagArg},
regex: `version ` + versionRegex,
},
rExeToolName: {
executable: rExeToolName,
args: []string{versionFlagArg},
regex: `version ` + versionRegex,
},
},
defaultTooling: []string{rscriptToolName, "R", rExeToolName},
versionURLTemplate: "https://www.r-project.org/",
},
"rust": {
extensions: []string{"*.rs", "Cargo.toml", "Cargo.lock"},
tooling: map[string]*cmd{
rustcToolName: {
executable: rustcToolName,
args: []string{versionFlagArg},
regex: `(rust version|rustc) (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+).(?P<patch>[0-9]+))(-(?P<prerelease>[a-z]+))?)(( \((?P<buildmetadata>[0-9a-f]+ [0-9]+-[0-9]+-[0-9]+)\))?)`, //nolint:lll
},
},
defaultTooling: []string{rustcToolName},
},
"swift": {
extensions: []string{"*.swift", "*.SWIFT", "Podfile"},
tooling: map[string]*cmd{
swiftToolName: {
executable: swiftToolName,
args: []string{versionFlagArg},
regex: `Swift version (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+)((.|-)(?P<patch>[0-9]+|dev))?))`,
},
},
defaultTooling: []string{swiftToolName},
versionURLTemplate: "https://github.com/apple/swift/releases/tag/swift-{{ .Full }}-RELEASE",
},
"v": {
extensions: []string{"*.v"},
tooling: map[string]*cmd{
"v": {
executable: "v",
args: []string{"--version"},
regex: `V (?P<version>(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)) [a-f0-9]+`,
},
},
defaultTooling: []string{"v"},
},
"vala": {
extensions: []string{"*.vala"},
tooling: map[string]*cmd{
valaToolName: {
executable: valaToolName,
args: []string{versionFlagArg},
regex: `Vala (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+).(?P<patch>[0-9]+)))`,
},
},
defaultTooling: []string{valaToolName},
versionURLTemplate: "https://gitlab.gnome.org/GNOME/vala/raw/{{ .Major }}.{{ .Minor }}/NEWS",
},
"zig": {
extensions: []string{"*.zig", "*.zon"},
projectFiles: []string{"build.zig"},
tooling: map[string]*cmd{
zigToolName: {
executable: zigToolName,
args: []string{versionArg},
regex: `(?P<version>(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)`, //nolint:lll
},
},
defaultTooling: []string{zigToolName},
versionURLTemplate: "https://ziglang.org/download/{{ .Major }}.{{ .Minor }}.{{ .Patch }}/release-notes.html",
},
"dart": {
extensions: []string{"*.dart", pubspecFileName, "pubspec.yml", "pubspec.lock"},
folders: []string{".dart_tool"},
tooling: map[string]*cmd{
fvmToolName: {
executable: fvmToolName,
args: []string{dartToolName, versionFlagArg},
regex: `Dart SDK version: ` + versionRegex,
},
dartToolName: {
executable: dartToolName,
args: []string{versionFlagArg},
regex: `Dart SDK version: ` + versionRegex,
},
},
defaultTooling: []string{fvmToolName, dartToolName},
versionURLTemplate: "https://dart.dev/guides/language/evolution#dart-{{ .Major }}{{ .Minor }}",
},
}
// ConfiguredLanguage is a generic Language segment driven entirely by data: a built-in preset
// looked up by name, and/or a user-supplied `tools` option. It backs both the migrated
// single-purpose segment types (which set name via NewLanguage) and the public
// "language" segment type (which reads name from the `name` option).
type ConfiguredLanguage struct {
Language
}
func (c *ConfiguredLanguage) Template() string {
return languageTemplate
}
// NewLanguage returns a ConfiguredLanguage preset to the built-in definition
// registered under name. Used by segment_types.go to back the migrated segment types.
func NewLanguage(name string) *ConfiguredLanguage {
c := &ConfiguredLanguage{}
c.name = name
return c
}
func (c *ConfiguredLanguage) Enabled() bool {
if c.name == "" {
c.name = c.options.String(LanguageName, "")
}
if preset, ok := languageDefinitions[c.name]; ok {
c.extensions = preset.extensions
c.projectFiles = preset.projectFiles
c.folders = preset.folders
c.tooling = preset.tooling
c.defaultTooling = preset.defaultTooling
c.versionURLTemplate = preset.versionURLTemplate
}
c.applyCustomTools()
enabled := c.Language.Enabled()
if preset, ok := languageDefinitions[c.name]; ok && preset.sanitizeVersion != nil {
c.Full = preset.sanitizeVersion(c.Full)
}
return enabled
}
// applyCustomTools reads the `tools` option (a list of {name, executable, args, regex,
// version_url_template}) and merges it into the tooling map. For presets, this lets a user
// add or override a tool. For the public "language" segment type, which has no preset, this
// is the only source of tooling, and its declaration order becomes the default tool order.
func (c *ConfiguredLanguage) applyCustomTools() {
raw, ok := c.options.Any(Tools, nil).([]any)
if !ok || len(raw) == 0 {
return
}
if c.tooling == nil {
c.tooling = make(map[string]*cmd, len(raw))
}
order := make([]string, 0, len(raw))
for _, item := range raw {
entry, ok := toStringMap(item)
if !ok {
continue
}
name := stringField(entry, "name")
if name == "" {
continue
}
c.tooling[name] = &cmd{
executable: stringField(entry, "executable"),
args: options.ParseStringArray(entry["args"]),
regex: stringField(entry, "regex"),
versionURLTemplate: stringField(entry, "version_url_template"),
}
order = append(order, name)
}
if _, hasPreset := languageDefinitions[c.name]; !hasPreset {
c.defaultTooling = order
}
}
func toStringMap(value any) (map[string]any, bool) {
switch v := value.(type) {
case map[string]any:
return v, true
case map[any]any:
out := make(map[string]any, len(v))
for key, val := range v {
out[fmt.Sprint(key)] = val
}
return out, true
default:
return nil, false
}
}
func stringField(entry map[string]any, key string) string {
value, ok := entry[key]
if !ok {
return ""
}
return fmt.Sprint(value)
}
+850
View File
@@ -0,0 +1,850 @@
package segments
import (
"errors"
"fmt"
"testing"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/mock"
"github.com/jandedobbeleer/oh-my-posh/src/segments/options"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/stretchr/testify/assert"
)
func TestConfiguredLanguageFortranPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{
Case: "GNU Fortran 10.2.1 Debian",
ExpectedString: "10.2.1",
Version: `GNU Fortran (Debian 10.2.1-6) 10.2.1 20210110
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.`,
},
{
Case: "GNU Fortran 11.4.0 Ubuntu",
ExpectedString: "11.4.0",
Version: `GNU Fortran (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.`,
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "gfortran",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.f",
}
env, props := getMockedLanguageEnv(params)
f := NewLanguage("fortran")
f.Init(props, env)
assert.True(t, f.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, f.Template(), f), tc.Case)
}
}
func TestConfiguredLanguageRubyPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
HasRbenv bool
HasRvmprompt bool
HasChruby bool
HasAsdf bool
HasRuby bool
HasRubyFiles bool
HasRakeFile bool
HasGemFile bool
FetchVersion bool
}{
{Case: "Ruby files", ExpectedString: "", FetchVersion: false, HasRubyFiles: true},
{Case: "Rakefile", ExpectedString: "", FetchVersion: false, HasRakeFile: true},
{Case: "Gemfile", ExpectedString: "", FetchVersion: false, HasGemFile: true},
{Case: "Gemfile with version", ExpectedString: "err parsing info from ruby with", FetchVersion: true, HasGemFile: true},
{
Case: "Version with chruby",
ExpectedString: "ruby-2.6.3",
FetchVersion: true,
HasRubyFiles: true,
HasChruby: true,
Version: ` * ruby-2.6.3
ruby-1.9.3-p392
jruby-1.7.0
rubinius-2.0.0-rc1`,
},
{
Case: "Version with chruby line 2",
ExpectedString: "ruby-1.9.3-p392",
FetchVersion: true,
HasRubyFiles: true,
HasChruby: true,
Version: ` ruby-2.6.3
* ruby-1.9.3-p392
jruby-1.7.0
rubinius-2.0.0-rc1`,
},
{
Case: "Version with asdf",
ExpectedString: "2.6.3",
FetchVersion: true,
HasRubyFiles: true,
HasAsdf: true,
Version: "ruby 2.6.3 /Users/jan/Projects/oh-my-posh/.tool-versions",
},
{
Case: "Version with asdf not set",
ExpectedString: "",
FetchVersion: true,
HasRubyFiles: true,
HasAsdf: true,
Version: "ruby ______ No version set. Run \"asdf <global|shell|local> ruby <version>\"",
},
{
Case: "Version with ruby",
ExpectedString: "2.6.3",
FetchVersion: true,
HasRubyFiles: true,
HasRuby: true,
Version: "ruby 2.6.3 (2019-04-16 revision 67580) [universal.x86_64-darwin20]",
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "ruby",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.rb",
}
env, props := getMockedLanguageEnv(params)
env.On("HasCommand", "rbenv").Return(tc.HasRbenv)
env.On("RunCommandWithEnv", "rbenv", []string(nil), []string{"version-name"}).Return(tc.Version, nil)
env.On("HasCommand", "rvm-prompt").Return(tc.HasRvmprompt)
env.On("RunCommandWithEnv", "rvm-prompt", []string(nil), []string{"i", "v", "g"}).Return(tc.Version, nil)
env.On("HasCommand", "chruby").Return(tc.HasChruby)
env.On("RunCommandWithEnv", "chruby", []string(nil), []string(nil)).Return(tc.Version, nil)
env.On("HasCommand", "asdf").Return(tc.HasAsdf)
env.On("RunCommandWithEnv", "asdf", []string(nil), []string{"current", "ruby"}).Return(tc.Version, nil)
env.On("HasFiles", "Rakefile").Return(tc.HasRakeFile)
env.On("HasFiles", "Gemfile").Return(tc.HasGemFile)
props[options.FetchVersion] = tc.FetchVersion
ruby := NewLanguage("ruby")
ruby.Init(props, env)
assert.True(t, ruby.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, ruby.Template(), ruby), tc.Case)
}
}
func TestConfiguredLanguageClojurePreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
Cmd string
}{
{
Case: "Clojure CLI 1.11.1.1113",
ExpectedString: "1.11.1.1113",
Version: "Clojure CLI version 1.11.1.1113",
Cmd: "clojure",
},
{
Case: "Leiningen 2.9.8",
ExpectedString: "2.9.8",
Version: "Leiningen 2.9.8 on Java 11.0.11 OpenJDK 64-Bit Server VM",
Cmd: "lein",
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: tc.Cmd,
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.clj",
}
env, props := getMockedLanguageEnv(params)
props[LanguageExtensions] = []string{params.extension}
if tc.Cmd != "clojure" {
env.On("HasCommand", "clojure").Return(false)
}
c := NewLanguage("clojure")
c.Init(props, env)
assert.True(t, c.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, c.Template(), c), tc.Case)
}
}
func TestConfiguredLanguageCrystalPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{Case: "Crystal 1.0.0", ExpectedString: "1.0.0", Version: "Crystal 1.0.0 (2021-03-22)"},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "crystal",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.cr",
}
env, props := getMockedLanguageEnv(params)
c := NewLanguage("crystal")
c.Init(props, env)
assert.True(t, c.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, c.Template(), c), tc.Case)
}
}
func TestConfiguredLanguageElixirPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
ElixirVersionOutput string
AsdfVersionOutput string
HasAsdf bool
AsdfExitCode int
}{
{
Case: "Version without asdf",
ExpectedString: "1.14.2",
ElixirVersionOutput: "Erlang/OTP 25 [erts-13.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]\n\nElixir 1.14.2 (compiled with Erlang/OTP 25)",
},
{
Case: "Version with asdf",
ExpectedString: "1.14.2",
HasAsdf: true,
AsdfVersionOutput: "elixir 1.14.2-otp-25 /path/to/.tool-versions",
ElixirVersionOutput: "Should not be used",
},
{
Case: "Version with asdf not set: should fall back to elixir --version",
ExpectedString: "1.14.2",
HasAsdf: true,
AsdfVersionOutput: "elixir ______ No version is set. Run \"asdf <global|shell|local> elixir <version>\"",
AsdfExitCode: 126,
ElixirVersionOutput: "Erlang/OTP 25 [erts-13.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]\n\nElixir 1.14.2 (compiled with Erlang/OTP 25)",
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "elixir",
versionParam: "--version",
versionOutput: tc.ElixirVersionOutput,
extension: "*.ex",
}
env, props := getMockedLanguageEnv(params)
env.On("HasCommand", "asdf").Return(tc.HasAsdf)
var asdfErr error
if tc.AsdfExitCode != 0 {
asdfErr = &runtime.CommandError{ExitCode: tc.AsdfExitCode}
}
env.On("RunCommandWithEnv", "asdf", []string(nil), []string{"current", "elixir"}).Return(tc.AsdfVersionOutput, asdfErr)
r := NewLanguage("elixir")
r.Init(props, env)
assert.True(t, r.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, r.Template(), r), tc.Case)
}
}
func TestConfiguredLanguageJuliaPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{Case: "Julia 1.6.0", ExpectedString: "1.6.0", Version: "julia version 1.6.0"},
{Case: "Julia 1.6.1", ExpectedString: "1.6.1", Version: "julia version 1.6.1"},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "julia",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.jl",
}
env, props := getMockedLanguageEnv(params)
j := NewLanguage("julia")
j.Init(props, env)
assert.True(t, j.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, j.Template(), j), tc.Case)
}
}
func TestConfiguredLanguageKotlinPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{Case: "Kotlin 1.6.10", ExpectedString: "1.6.10", Version: "Kotlin version 1.6.10-release-923 (JRE 17.0.2+0)"},
{Case: "Kotlin 1.6.0", ExpectedString: "1.6.0", Version: "Kotlin version 1.6.0-release-915 (JRE 17.0.2+0)"},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "kotlin",
versionParam: "-version",
versionOutput: tc.Version,
extension: "*.kt",
}
env, props := getMockedLanguageEnv(params)
k := NewLanguage("kotlin")
k.Init(props, env)
assert.True(t, k.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, k.Template(), k), tc.Case)
}
}
func TestConfiguredLanguageLuaPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
ExpectedURL string
Tooling []string
HasLua bool
HasLuaJit bool
}{
{
Case: "Lua 5.4.4 - default tooling prefers lua",
ExpectedString: "5.4.4",
Version: "Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio",
ExpectedURL: "https://www.lua.org/manual/5.4/readme.html#changes",
HasLua: true,
HasLuaJit: true,
},
{
Case: "Lua 5.0 - tooling set to luajit but missing so fallback to lua",
ExpectedString: "5.0",
Version: "Lua 5.0 Copyright (C) 1994-2003 Tecgraf, PUC-Rio",
ExpectedURL: "https://www.lua.org/manual/5.0/readme.html#changes",
HasLua: true,
Tooling: []string{"luajit", "lua"},
},
{
Case: "LuaJIT 2.0.5 - tooling set to luajit first",
ExpectedString: "2.0.5",
Version: "LuaJIT 2.0.5 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/",
HasLuaJit: true,
HasLua: true,
ExpectedURL: "https://github.com/LuaJIT/LuaJIT/tree/v2.0",
Tooling: []string{"luajit"},
},
{
Case: "LuaJIT 2.1.0-beta3 - tooling set to lua first but missing so try luajit",
ExpectedString: "2.1.0",
Version: "LuaJIT 2.1.0-beta3 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/",
HasLuaJit: true,
ExpectedURL: "https://github.com/LuaJIT/LuaJIT/tree/v2.1",
Tooling: []string{"lua", "luajit"},
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "lua",
versionParam: "-v",
versionOutput: tc.Version,
extension: "*.lua",
}
env, props := getMockedLanguageEnv(params)
if !tc.HasLua {
env.Unset("HasCommand")
env.On("HasCommand", "lua").Return(false)
}
env.On("HasCommand", "luajit").Return(tc.HasLuaJit)
env.On("RunCommandWithEnv", "luajit", []string(nil), []string{"-v"}).Return(tc.Version, nil)
env.On("Shell").Return("bash")
// Initialize template system for version URL rendering
if template.Cache == nil {
template.Cache = &cache.Template{}
}
template.Init(env, nil, nil)
if len(tc.Tooling) > 0 {
props[Tooling] = tc.Tooling
}
l := NewLanguage("lua")
l.Init(props, env)
failMsg := tc.Case
assert.True(t, l.Enabled(), failMsg)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, l.Template(), l), failMsg)
assert.Equal(t, tc.ExpectedURL, l.URL, failMsg)
}
}
func TestConfiguredLanguageNimPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{
Case: "Nim 2.2.0",
ExpectedString: "2.2.0",
Version: "Nim Compiler Version 2.2.0 [MacOSX: arm64]\nCompiled at 2024-11-30\nCopyright (c) 2006-2024 by Andreas Rumpf",
},
{
Case: "Nim 1.6.12",
ExpectedString: "1.6.12",
Version: "Nim Compiler Version 1.6.12 [Linux: amd64]\nCompiled at 2023-06-15\nCopyright (c) 2006-2023 by Andreas Rumpf",
},
{
Case: "Nim 2.0.0",
ExpectedString: "2.0.0",
Version: "Nim Compiler Version 2.0.0 [Windows: amd64]\nCompiled at 2023-12-25\nCopyright (c) 2006-2023 by Andreas Rumpf",
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "nim",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.nim",
}
env, props := getMockedLanguageEnv(params)
n := NewLanguage("nim")
n.Init(props, env)
assert.True(t, n.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, n.Template(), n), tc.Case)
}
}
func TestConfiguredLanguageOCamlPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{Case: "OCaml 4.12.0", ExpectedString: "4.12.0", Version: "The OCaml toplevel, version 4.12.0"},
{Case: "OCaml 4.11.0", ExpectedString: "4.11.0", Version: "The OCaml toplevel, version 4.11.0"},
{Case: "OCaml 4.13.0", ExpectedString: "4.13.0", Version: "The OCaml toplevel, version 4.13.0"},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "ocaml",
versionParam: "-version",
versionOutput: tc.Version,
extension: "*.ml",
}
env, props := getMockedLanguageEnv(params)
o := NewLanguage("ocaml")
o.Init(props, env)
assert.True(t, o.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, o.Template(), o), tc.Case)
}
}
func TestConfiguredLanguagePerlPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{
Case: "v5.12+",
ExpectedString: "5.32.1",
Version: "This is perl 5, version 32, subversion 1 (v5.32.1) built for MSWin32-x64-multi-thread",
},
{
Case: "v5.6 - v5.10",
ExpectedString: "5.6.1",
Version: "This is perl, v5.6.1 built for MSWin32-x86-multi-thread",
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "perl",
versionParam: "-version",
versionOutput: tc.Version,
extension: ".perl-version",
}
env, props := getMockedLanguageEnv(params)
p := NewLanguage("perl")
p.Init(props, env)
assert.True(t, p.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, p.Template(), p), tc.Case)
}
}
func TestConfiguredLanguagePhpPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{Case: "PHP 6.1.0", ExpectedString: "6.1.0", Version: "PHP 6.1.0(cli) (built: Jul 2 2021 03:59:48) ( NTS )"},
{Case: "php 7.4.21", ExpectedString: "7.4.21", Version: "PHP 7.4.21 (cli) (built: Jul 2 2021 03:59:48) ( NTS )"},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "php",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.php",
}
env, props := getMockedLanguageEnv(params)
j := NewLanguage("php")
j.Init(props, env)
assert.True(t, j.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, j.Template(), j), tc.Case)
}
}
func TestConfiguredLanguageRPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
HasRscript bool
HasR bool
HasRexe bool
}{
{Case: "Rscript 4.2.0", ExpectedString: "4.2.0", HasRscript: true, Version: "Rscript (R) version 4.2.0 (2022-04-22)"},
{Case: "Rscript 4.1.3", ExpectedString: "4.1.3", HasRscript: true, Version: "R scripting front-end version 4.1.3 (2022-03-10)"},
{Case: "Rscript 4.1.3 patched", ExpectedString: "4.1.3", HasRscript: true, Version: "R scripting front-end version 4.1.3 Patched (2022-03-10 r81896)"},
{Case: "Rscript 4.0.0", ExpectedString: "4.0.0", HasRscript: true, Version: "R scripting front-end version 4.0.0 (2020-04-24)"},
{Case: "Rscript devel", ExpectedString: "4.2.0", HasRscript: true, Version: "R scripting front-end version 4.2.0 Under development (unstable) (2022-03-14 r81896)"},
{Case: "R 4.1.2", ExpectedString: "4.1.2", HasR: true, Version: "R version 4.1.2 (2021-11-01) -- \"Bird Hippie\""},
{Case: "R 4.1.3 patched", ExpectedString: "4.1.3", HasR: true, Version: "R version 4.1.3 Patched (2022-03-10 r81896) -- \"One Push-Up\""},
{Case: "R 4.0.0", ExpectedString: "4.0.0", HasR: true, Version: "R version 4.0.0 (2020-04-24) -- \"Arbor Day\""},
{Case: "R.exe 4.1.2", ExpectedString: "4.1.2", HasRexe: true, Version: "R version 4.1.2 (2021-11-01) -- \"Bird Hippie\""},
{Case: "R.exe 4.1.3 patched", ExpectedString: "4.1.3", HasRexe: true, Version: "R version 4.1.3 Patched (2022-03-10 r81896) -- \"One Push-Up\""},
{Case: "R.exe 4.0.0", ExpectedString: "4.0.0", HasRexe: true, Version: "R version 4.0.0 (2020-04-24) -- \"Arbor Day\""},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "R",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.R",
}
env, props := getMockedLanguageEnv(params)
env.On("HasCommand", "Rscript").Return(tc.HasRscript)
env.On("RunCommandWithEnv", "Rscript", []string(nil), []string{"--version"}).Return(tc.Version, nil)
env.On("HasCommand", "R.exe").Return(tc.HasRexe)
env.On("RunCommandWithEnv", "R.exe", []string(nil), []string{"--version"}).Return(tc.Version, nil)
r := NewLanguage("r")
r.Init(props, env)
assert.True(t, r.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, r.Template(), r), tc.Case)
}
}
func TestConfiguredLanguageRustPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{Case: "Rust 1.64.0", ExpectedString: "1.64.0", Version: "rustc 1.64.0"},
{Case: "Rust 1.53.0", ExpectedString: "1.53.0", Version: "rustc 1.53.0 (4369396ce 2021-04-27)"},
{Case: "Rust 1.66.0", ExpectedString: "1.66.0-nightly", Version: "rustc 1.66.0-nightly (01af5040f 2022-10-04)"},
{
Case: "Toolchain not installed",
ExpectedString: "1.81.0",
Version: ` info: syncing channel updates for '1.81.0-x86_64-pc-windows-msvc'
info: latest update on 2024-09-05, rust version 1.81.0 (eeb90cda1 2024-09-04)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-analyzer'
info: downloading component 'rust-src'
info: downloading component 'rust-std'
info: downloading component 'rustc'
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-analyzer'
info: installing component 'rust-src'
info: installing component 'rust-std'
info: installing component 'rustc'`,
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "rustc",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.rs",
}
env, props := getMockedLanguageEnv(params)
r := NewLanguage("rust")
r.Init(props, env)
assert.True(t, r.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, r.Template(), r), tc.Case)
}
}
func TestConfiguredLanguageSwiftPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{Case: "Swift 5.5.3", ExpectedString: "5.5.3", Version: "Swift version 5.5.3 (swift-5.5.3-RELEASE)"},
{Case: "Swift 5.5.3 on Windows", ExpectedString: "5.5.3", Version: "compnerd.org Swift version 5.5.3 (swift-5.5.3-RELEASE)"},
{Case: "Swift 5.5.3 on Mac", ExpectedString: "5.5.3", Version: "Apple Swift version 5.5.3 (swift-5.5.3-RELEASE)"},
{Case: "Swift 5.5", ExpectedString: "5.5", Version: "Swift version 5.5 (swift-5.5-RELEASE)"},
{Case: "Swift 5.6-dev", ExpectedString: "5.6-dev", Version: "Swift version 5.6-dev (LLVM 62b900d3d0d5be9, Swift ce64fe8867792d4)"},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "swift",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.swift",
}
env, props := getMockedLanguageEnv(params)
s := NewLanguage("swift")
s.Init(props, env)
assert.True(t, s.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, s.Template(), s), tc.Case)
}
}
func TestConfiguredLanguageVPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{
Case: "V 0.4.9",
ExpectedString: "0.4.9",
Version: "V 0.4.9 b487986",
},
{
Case: "V 0.4.8",
ExpectedString: "0.4.8",
Version: "V 0.4.8 a123456",
},
{
Case: "V 0.4.7",
ExpectedString: "0.4.7",
Version: "V 0.4.7 f789012",
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "v",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.v",
}
env, props := getMockedLanguageEnv(params)
v := NewLanguage("v")
v.Init(props, env)
assert.True(t, v.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, v.Template(), v), tc.Case)
}
}
func TestConfiguredLanguageValaPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{
Case: "vala 0.48.17",
ExpectedString: "0.48.17",
Version: "Vala 0.48.17",
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "vala",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.vala",
}
env, props := getMockedLanguageEnv(params)
v := NewLanguage("vala")
v.Init(props, env)
assert.True(t, v.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, v.Template(), v), tc.Case)
}
}
// TestConfiguredLanguageCustomTools exercises the `tools` option path used by the public
// "language" segment type, which has no built-in preset and relies entirely on user config.
func TestConfiguredLanguageCustomTools(t *testing.T) {
env := new(mock.Environment)
env.On("HasCommand", "mytool").Return(true)
env.On("RunCommandWithEnv", "mytool", []string(nil), []string{"--version"}).Return("mytool version 1.2.3", nil)
env.On("HasFiles", "*.myl").Return(true)
env.On("Pwd").Return("/usr/home/project")
env.On("Home").Return("/usr/home")
props := options.Map{
options.FetchVersion: true,
LanguageName: "mylang",
LanguageExtensions: []string{"*.myl"},
Tools: []any{
map[string]any{
"name": "mytool",
"executable": "mytool",
"args": []any{"--version"},
"regex": `mytool version (?P<version>(?P<major>[0-9]+).(?P<minor>[0-9]+).(?P<patch>[0-9]+))`,
},
},
}
l := &ConfiguredLanguage{}
l.Init(props, env)
assert.True(t, l.Enabled())
assert.Equal(t, "1.2.3", renderTemplate(env, l.Template(), l))
assert.Equal(t, []string{"mytool"}, l.defaultTooling)
}
func TestConfiguredLanguageZigPreset(t *testing.T) {
cases := []struct {
Case string
Version string
ExpectedString string
ExpectedURL string
InProjectDir bool
}{
{
Case: "zig 0.13.0 - not in project dir",
Version: "0.13.0",
InProjectDir: false,
ExpectedString: "0.13.0",
ExpectedURL: "https://ziglang.org/download/0.13.0/release-notes.html",
},
{
Case: "zig 0.12.0-dev.2063+804cee3b9 - not in project dir",
Version: "0.12.0-dev.2063+804cee3b9",
InProjectDir: false,
ExpectedString: "0.12.0-dev.2063+804cee3b9",
ExpectedURL: "https://ziglang.org/download/0.12.0/release-notes.html",
},
{
Case: "zig 0.13.0 - in project dir",
Version: "0.13.0",
InProjectDir: true,
ExpectedString: "0.13.0",
ExpectedURL: "https://ziglang.org/download/0.13.0/release-notes.html",
},
{
Case: "zig 0.12.0-dev.2063+804cee3b9 - in project dir",
Version: "0.12.0-dev.2063+804cee3b9",
InProjectDir: true,
ExpectedString: "0.12.0-dev.2063+804cee3b9",
ExpectedURL: "https://ziglang.org/download/0.12.0/release-notes.html",
},
}
// Initialize template system for version URL rendering
if template.Cache == nil {
template.Cache = &cache.Template{}
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "zig",
versionParam: "version",
versionOutput: tc.Version,
extension: "*.zig",
}
env, props := getMockedLanguageEnv(params)
env.On("Shell").Return("bash")
template.Init(env, nil, nil)
dummyDir := &runtime.FileInfo{}
if tc.InProjectDir {
env.On("HasParentFilePath", "build.zig", false).Return(dummyDir, nil)
} else {
env.On("HasParentFilePath", "build.zig", false).Return(dummyDir, errors.New("build.zig not found"))
}
zig := NewLanguage("zig")
zig.Init(props, env)
assert.True(t, zig.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, zig.Template(), zig), tc.Case)
assert.Equal(t, tc.ExpectedURL, renderTemplate(env, zig.URL, zig), tc.Case)
assert.Equal(t, tc.InProjectDir, zig.InProjectDir(), tc.Case)
}
}
func TestConfiguredLanguageDartPreset(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{Case: "Dart 2.12.4", ExpectedString: "2.12.4", Version: "Dart SDK version: 2.12.4 (stable) (Thu Apr 15 12:26:53 2021 +0200) on \"macos_x64\""},
}
// Initialize template system for version URL rendering
if template.Cache == nil {
template.Cache = &cache.Template{}
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "dart",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.dart",
}
env, props := getMockedLanguageEnv(params)
env.On("Shell").Return("bash")
template.Init(env, nil, nil)
env.On("HasCommand", "fvm").Return(false)
d := NewLanguage("dart")
d.Init(props, env)
assert.True(t, d.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, d.Template(), d), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
-36
View File
@@ -1,36 +0,0 @@
package segments
type Lua struct {
Language
}
func (l *Lua) Template() string {
return languageTemplate
}
func (l *Lua) Enabled() bool {
const (
luaToolName = "lua"
luajitToolName = "luajit"
)
l.extensions = []string{"*.lua", "*.rockspec"}
l.folders = []string{"lua"}
l.tooling = map[string]*cmd{
luaToolName: {
executable: luaToolName,
args: []string{"-v"},
regex: `Lua (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+)(.(?P<patch>[0-9]+))?))`,
versionURLTemplate: "https://www.lua.org/manual/{{ .Major }}.{{ .Minor }}/readme.html#changes",
},
luajitToolName: {
executable: luajitToolName,
args: []string{"-v"},
regex: `LuaJIT (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+)(.(?P<patch>[0-9]+))?))`,
versionURLTemplate: "https://github.com/LuaJIT/LuaJIT/tree/v{{ .Major}}.{{ .Minor}}",
},
}
l.defaultTooling = []string{luaToolName, luajitToolName}
return l.Language.Enabled()
}
-92
View File
@@ -1,92 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/stretchr/testify/assert"
)
func TestLua(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
ExpectedURL string
Tooling []string
HasLua bool
HasLuaJit bool
}{
{
Case: "Lua 5.4.4 - default tooling prefers lua",
ExpectedString: "5.4.4",
Version: "Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio",
ExpectedURL: "https://www.lua.org/manual/5.4/readme.html#changes",
HasLua: true,
HasLuaJit: true,
},
{
Case: "Lua 5.0 - tooling set to luajit but missing so fallback to lua",
ExpectedString: "5.0",
Version: "Lua 5.0 Copyright (C) 1994-2003 Tecgraf, PUC-Rio",
ExpectedURL: "https://www.lua.org/manual/5.0/readme.html#changes",
HasLua: true,
Tooling: []string{"luajit", "lua"},
},
{
Case: "LuaJIT 2.0.5 - tooling set to luajit first",
ExpectedString: "2.0.5",
Version: "LuaJIT 2.0.5 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/",
HasLuaJit: true,
HasLua: true,
ExpectedURL: "https://github.com/LuaJIT/LuaJIT/tree/v2.0",
Tooling: []string{"luajit"},
},
{
Case: "LuaJIT 2.1.0-beta3 - tooling set to lua first but missing so try luajit",
ExpectedString: "2.1.0",
Version: "LuaJIT 2.1.0-beta3 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/",
HasLuaJit: true,
ExpectedURL: "https://github.com/LuaJIT/LuaJIT/tree/v2.1",
Tooling: []string{"lua", "luajit"},
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "lua",
versionParam: "-v",
versionOutput: tc.Version,
extension: "*.lua",
}
env, props := getMockedLanguageEnv(params)
if !tc.HasLua {
env.Unset("HasCommand")
env.On("HasCommand", "lua").Return(false)
}
env.On("HasCommand", "luajit").Return(tc.HasLuaJit)
env.On("RunCommandWithEnv", "luajit", []string(nil), []string{"-v"}).Return(tc.Version, nil)
env.On("Shell").Return("bash")
// Initialize template system for version URL rendering
if template.Cache == nil {
template.Cache = &cache.Template{}
}
template.Init(env, nil, nil)
if len(tc.Tooling) > 0 {
props[Tooling] = tc.Tooling
}
l := &Lua{}
l.Init(props, env)
failMsg := fmt.Sprintf("Failed in case: %s", tc.Case)
assert.True(t, l.Enabled(), failMsg)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, l.Template(), l), failMsg)
assert.Equal(t, tc.ExpectedURL, l.URL, failMsg)
}
}
-25
View File
@@ -1,25 +0,0 @@
package segments
type Nim struct {
Language
}
func (n *Nim) Template() string {
return languageTemplate
}
func (n *Nim) Enabled() bool {
const nimToolName = "nim"
n.extensions = []string{"*.nim", "*.nims"}
n.tooling = map[string]*cmd{
nimToolName: {
executable: nimToolName,
args: []string{versionFlagArg},
regex: `Nim Compiler Version (?P<version>(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+))`,
},
}
n.defaultTooling = []string{nimToolName}
return n.Language.Enabled()
}
-46
View File
@@ -1,46 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestNim(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{
Case: "Nim 2.2.0",
ExpectedString: "2.2.0",
Version: "Nim Compiler Version 2.2.0 [MacOSX: arm64]\nCompiled at 2024-11-30\nCopyright (c) 2006-2024 by Andreas Rumpf",
},
{
Case: "Nim 1.6.12",
ExpectedString: "1.6.12",
Version: "Nim Compiler Version 1.6.12 [Linux: amd64]\nCompiled at 2023-06-15\nCopyright (c) 2006-2023 by Andreas Rumpf",
},
{
Case: "Nim 2.0.0",
ExpectedString: "2.0.0",
Version: "Nim Compiler Version 2.0.0 [Windows: amd64]\nCompiled at 2023-12-25\nCopyright (c) 2006-2023 by Andreas Rumpf",
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "nim",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.nim",
}
env, props := getMockedLanguageEnv(params)
n := &Nim{}
n.Init(props, env)
assert.True(t, n.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, n.Template(), n), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
-25
View File
@@ -1,25 +0,0 @@
package segments
type OCaml struct {
Language
}
func (o *OCaml) Template() string {
return languageTemplate
}
func (o *OCaml) Enabled() bool {
const ocamlToolName = "ocaml"
o.extensions = []string{"*.ml", "*.mli", "dune", "dune-project", "dune-workspace"}
o.tooling = map[string]*cmd{
ocamlToolName: {
executable: ocamlToolName,
args: []string{versionFlagShortArg},
regex: `The OCaml toplevel, version (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+).(?P<patch>[0-9]+))(-(?P<prerelease>[a-z]+))?)`,
},
}
o.defaultTooling = []string{ocamlToolName}
return o.Language.Enabled()
}
-33
View File
@@ -1,33 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestOCaml(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{Case: "OCaml 4.12.0", ExpectedString: "4.12.0", Version: "The OCaml toplevel, version 4.12.0"},
{Case: "OCaml 4.11.0", ExpectedString: "4.11.0", Version: "The OCaml toplevel, version 4.11.0"},
{Case: "OCaml 4.13.0", ExpectedString: "4.13.0", Version: "The OCaml toplevel, version 4.13.0"},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "ocaml",
versionParam: "-version",
versionOutput: tc.Version,
extension: "*.ml",
}
env, props := getMockedLanguageEnv(params)
o := &OCaml{}
o.Init(props, env)
assert.True(t, o.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, o.Template(), o), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
-31
View File
@@ -1,31 +0,0 @@
package segments
type Perl struct {
Language
}
func (p *Perl) Template() string {
return languageTemplate
}
func (p *Perl) Enabled() bool {
const perlToolName = "perl"
perlRegex := `This is perl.*v(?P<version>(?P<major>[0-9]+)(?:\.(?P<minor>[0-9]+))(?:\.(?P<patch>[0-9]+))?).* built for .+`
p.extensions = []string{
".perl-version",
"*.pl",
"*.pm",
"*.t",
}
p.tooling = map[string]*cmd{
perlToolName: {
executable: perlToolName,
args: []string{versionFlagShortArg},
regex: perlRegex,
},
}
p.defaultTooling = []string{perlToolName}
return p.Language.Enabled()
}
-43
View File
@@ -1,43 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPerl(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
PerlHomeVersion string
PerlHomeEnabled bool
}{
{
Case: "v5.12+",
ExpectedString: "5.32.1",
Version: "This is perl 5, version 32, subversion 1 (v5.32.1) built for MSWin32-x64-multi-thread",
},
{
Case: "v5.6 - v5.10",
ExpectedString: "5.6.1",
Version: "This is perl, v5.6.1 built for MSWin32-x86-multi-thread",
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "perl",
versionParam: "-version",
versionOutput: tc.Version,
extension: ".perl-version",
}
env, props := getMockedLanguageEnv(params)
p := &Perl{}
p.Init(props, env)
assert.True(t, p.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, p.Template(), p), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
-24
View File
@@ -1,24 +0,0 @@
package segments
type Php struct {
Language
}
func (p *Php) Template() string {
return languageTemplate
}
func (p *Php) Enabled() bool {
p.extensions = []string{"*.php", "composer.json", "composer.lock", ".php-version", "blade.php"}
p.tooling = map[string]*cmd{
phpToolName: {
executable: phpToolName,
args: []string{versionFlagArg},
regex: `(?:PHP (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+).(?P<patch>[0-9]+))))`,
},
}
p.defaultTooling = []string{phpToolName}
p.versionURLTemplate = "https://www.php.net/ChangeLog-{{ .Major }}.php#PHP_{{ .Major }}_{{ .Minor }}"
return p.Language.Enabled()
}
-32
View File
@@ -1,32 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPhp(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{Case: "PHP 6.1.0", ExpectedString: "6.1.0", Version: "PHP 6.1.0(cli) (built: Jul 2 2021 03:59:48) ( NTS )"},
{Case: "php 7.4.21", ExpectedString: "7.4.21", Version: "PHP 7.4.21 (cli) (built: Jul 2 2021 03:59:48) ( NTS )"},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "php",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.php",
}
env, props := getMockedLanguageEnv(params)
j := &Php{}
j.Init(props, env)
assert.True(t, j.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, j.Template(), j), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
+1 -1
View File
@@ -112,7 +112,7 @@ func (n *Project) Enabled() bool {
},
{
Name: dartToolName,
Files: []string{"pubspec.yaml"},
Files: []string{pubspecFileName},
Fetcher: n.getDartPackage,
},
{
-40
View File
@@ -1,40 +0,0 @@
package segments
type R struct {
Language
}
func (r *R) Template() string {
return languageTemplate
}
const (
rscriptToolName = "Rscript"
rExeToolName = "R.exe"
)
func (r *R) Enabled() bool {
rRegex := `version ` + versionRegex
r.extensions = []string{"*.R", "*.Rmd", "*.Rsx", "*.Rda", "*.Rd", "*.Rproj", ".Rproj.user"}
r.tooling = map[string]*cmd{
rscriptToolName: {
executable: rscriptToolName,
args: []string{versionFlagArg},
regex: rRegex,
},
"R": {
executable: "R",
args: []string{versionFlagArg},
regex: rRegex,
},
rExeToolName: {
executable: rExeToolName,
args: []string{versionFlagArg},
regex: rRegex,
},
}
r.defaultTooling = []string{rscriptToolName, "R", rExeToolName}
r.versionURLTemplate = "https://www.r-project.org/"
return r.Language.Enabled()
}
-53
View File
@@ -1,53 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestR(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
HasRscript bool
HasR bool
HasRexe bool
}{
{Case: "Rscript 4.2.0", ExpectedString: "4.2.0", HasRscript: true, Version: "Rscript (R) version 4.2.0 (2022-04-22)"},
{Case: "Rscript 4.1.3", ExpectedString: "4.1.3", HasRscript: true, Version: "R scripting front-end version 4.1.3 (2022-03-10)"},
{Case: "Rscript 4.1.3 patched", ExpectedString: "4.1.3", HasRscript: true, Version: "R scripting front-end version 4.1.3 Patched (2022-03-10 r81896)"},
{Case: "Rscript 4.0.0", ExpectedString: "4.0.0", HasRscript: true, Version: "R scripting front-end version 4.0.0 (2020-04-24)"},
{Case: "Rscript devel", ExpectedString: "4.2.0", HasRscript: true, Version: "R scripting front-end version 4.2.0 Under development (unstable) (2022-03-14 r81896)"},
{Case: "R 4.1.2", ExpectedString: "4.1.2", HasR: true, Version: "R version 4.1.2 (2021-11-01) -- \"Bird Hippie\""},
{Case: "R 4.1.3 patched", ExpectedString: "4.1.3", HasR: true, Version: "R version 4.1.3 Patched (2022-03-10 r81896) -- \"One Push-Up\""},
{Case: "R 4.0.0", ExpectedString: "4.0.0", HasR: true, Version: "R version 4.0.0 (2020-04-24) -- \"Arbor Day\""},
{Case: "R.exe 4.1.2", ExpectedString: "4.1.2", HasRexe: true, Version: "R version 4.1.2 (2021-11-01) -- \"Bird Hippie\""},
{Case: "R.exe 4.1.3 patched", ExpectedString: "4.1.3", HasRexe: true, Version: "R version 4.1.3 Patched (2022-03-10 r81896) -- \"One Push-Up\""},
{Case: "R.exe 4.0.0", ExpectedString: "4.0.0", HasRexe: true, Version: "R version 4.0.0 (2020-04-24) -- \"Arbor Day\""},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "R",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.R",
}
env, props := getMockedLanguageEnv(params)
env.On("HasCommand", "Rscript").Return(tc.HasRscript)
env.On("RunCommandWithEnv", "Rscript", []string(nil), []string{"--version"}).Return(tc.Version, nil)
env.On("HasCommand", "R.exe").Return(tc.HasRexe)
env.On("RunCommandWithEnv", "R.exe", []string(nil), []string{"--version"}).Return(tc.Version, nil)
r := &R{}
r.Init(props, env)
assert.True(t, r.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, r.Template(), r), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
-57
View File
@@ -1,57 +0,0 @@
package segments
type Ruby struct {
Language
}
func (r *Ruby) Template() string {
return languageTemplate
}
const (
chrubyToolName = "chruby"
rbenvToolName = "rbenv"
rvmPromptToolName = "rvm-prompt"
rubyToolName = "ruby"
)
func (r *Ruby) Enabled() bool {
r.extensions = []string{"*.rb", "Rakefile", "Gemfile"}
r.tooling = map[string]*cmd{
rbenvToolName: {
executable: rbenvToolName,
args: []string{"version-name"},
regex: `(?P<version>.+)`,
},
rvmPromptToolName: {
executable: rvmPromptToolName,
args: []string{"i", "v", "g"},
regex: `(?P<version>.+)`,
},
chrubyToolName: {
executable: chrubyToolName,
args: []string(nil),
regex: `\* (?P<version>.+)\n`,
},
asdfToolName: {
executable: asdfToolName,
args: []string{"current", rubyToolName},
regex: `ruby\s+(?P<version>[^\s]+)\s+`,
},
rubyToolName: {
executable: rubyToolName,
args: []string{versionFlagArg},
regex: `ruby\s+(?P<version>[^\s]+)\s+`,
},
}
r.defaultTooling = []string{rbenvToolName, rvmPromptToolName, chrubyToolName, asdfToolName, rubyToolName}
enabled := r.Language.Enabled()
// this happens when no version is set
if r.Full == "______" {
r.Full = ""
}
return enabled
}
-105
View File
@@ -1,105 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/jandedobbeleer/oh-my-posh/src/segments/options"
"github.com/stretchr/testify/assert"
)
func TestRuby(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
HasRbenv bool
HasRvmprompt bool
HasChruby bool
HasAsdf bool
HasRuby bool
HasRubyFiles bool
HasRakeFile bool
HasGemFile bool
FetchVersion bool
}{
{Case: "Ruby files", ExpectedString: "", FetchVersion: false, HasRubyFiles: true},
{Case: "Rakefile", ExpectedString: "", FetchVersion: false, HasRakeFile: true},
{Case: "Gemfile", ExpectedString: "", FetchVersion: false, HasGemFile: true},
{Case: "Gemfile with version", ExpectedString: "err parsing info from ruby with", FetchVersion: true, HasGemFile: true},
{
Case: "Version with chruby",
ExpectedString: "ruby-2.6.3",
FetchVersion: true,
HasRubyFiles: true,
HasChruby: true,
Version: ` * ruby-2.6.3
ruby-1.9.3-p392
jruby-1.7.0
rubinius-2.0.0-rc1`,
},
{
Case: "Version with chruby line 2",
ExpectedString: "ruby-1.9.3-p392",
FetchVersion: true,
HasRubyFiles: true,
HasChruby: true,
Version: ` ruby-2.6.3
* ruby-1.9.3-p392
jruby-1.7.0
rubinius-2.0.0-rc1`,
},
{
Case: "Version with asdf",
ExpectedString: "2.6.3",
FetchVersion: true,
HasRubyFiles: true,
HasAsdf: true,
Version: "ruby 2.6.3 /Users/jan/Projects/oh-my-posh/.tool-versions",
},
{
Case: "Version with asdf not set",
ExpectedString: "",
FetchVersion: true,
HasRubyFiles: true,
HasAsdf: true,
Version: "ruby ______ No version set. Run \"asdf <global|shell|local> ruby <version>\"",
},
{
Case: "Version with ruby",
ExpectedString: "2.6.3",
FetchVersion: true,
HasRubyFiles: true,
HasRuby: true,
Version: "ruby 2.6.3 (2019-04-16 revision 67580) [universal.x86_64-darwin20]",
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "ruby",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.rb",
}
env, props := getMockedLanguageEnv(params)
env.On("HasCommand", "rbenv").Return(tc.HasRbenv)
env.On("RunCommandWithEnv", "rbenv", []string(nil), []string{"version-name"}).Return(tc.Version, nil)
env.On("HasCommand", "rvm-prompt").Return(tc.HasRvmprompt)
env.On("RunCommandWithEnv", "rvm-prompt", []string(nil), []string{"i", "v", "g"}).Return(tc.Version, nil)
env.On("HasCommand", "chruby").Return(tc.HasChruby)
env.On("RunCommandWithEnv", "chruby", []string(nil), []string(nil)).Return(tc.Version, nil)
env.On("HasCommand", "asdf").Return(tc.HasAsdf)
env.On("RunCommandWithEnv", "asdf", []string(nil), []string{"current", "ruby"}).Return(tc.Version, nil)
env.On("HasFiles", "Rakefile").Return(tc.HasRakeFile)
env.On("HasFiles", "Gemfile").Return(tc.HasGemFile)
props[options.FetchVersion] = tc.FetchVersion
ruby := &Ruby{}
ruby.Init(props, env)
assert.True(t, ruby.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, ruby.Template(), ruby), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
-25
View File
@@ -1,25 +0,0 @@
package segments
type Rust struct {
Language
}
func (r *Rust) Template() string {
return languageTemplate
}
func (r *Rust) Enabled() bool {
const rustcToolName = "rustc"
r.extensions = []string{"*.rs", "Cargo.toml", "Cargo.lock"}
r.tooling = map[string]*cmd{
rustcToolName: {
executable: rustcToolName,
args: []string{versionFlagArg},
regex: `(rust version|rustc) (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+).(?P<patch>[0-9]+))(-(?P<prerelease>[a-z]+))?)(( \((?P<buildmetadata>[0-9a-f]+ [0-9]+-[0-9]+-[0-9]+)\))?)`, //nolint:lll
},
}
r.defaultTooling = []string{rustcToolName}
return r.Language.Enabled()
}
-52
View File
@@ -1,52 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestRust(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{Case: "Rust 1.64.0", ExpectedString: "1.64.0", Version: "rustc 1.64.0"},
{Case: "Rust 1.53.0", ExpectedString: "1.53.0", Version: "rustc 1.53.0 (4369396ce 2021-04-27)"},
{Case: "Rust 1.66.0", ExpectedString: "1.66.0-nightly", Version: "rustc 1.66.0-nightly (01af5040f 2022-10-04)"},
{
Case: "Toolchain not installed",
ExpectedString: "1.81.0",
Version: ` info: syncing channel updates for '1.81.0-x86_64-pc-windows-msvc'
info: latest update on 2024-09-05, rust version 1.81.0 (eeb90cda1 2024-09-04)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-analyzer'
info: downloading component 'rust-src'
info: downloading component 'rust-std'
info: downloading component 'rustc'
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-analyzer'
info: installing component 'rust-src'
info: installing component 'rust-std'
info: installing component 'rustc'`,
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "rustc",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.rs",
}
env, props := getMockedLanguageEnv(params)
r := &Rust{}
r.Init(props, env)
assert.True(t, r.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, r.Template(), r), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
-26
View File
@@ -1,26 +0,0 @@
package segments
type Swift struct {
Language
}
func (s *Swift) Template() string {
return languageTemplate
}
func (s *Swift) Enabled() bool {
const swiftToolName = "swift"
s.extensions = []string{"*.swift", "*.SWIFT", "Podfile"}
s.tooling = map[string]*cmd{
swiftToolName: {
executable: swiftToolName,
args: []string{versionFlagArg},
regex: `Swift version (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+)((.|-)(?P<patch>[0-9]+|dev))?))`,
},
}
s.defaultTooling = []string{swiftToolName}
s.versionURLTemplate = "https://github.com/apple/swift/releases/tag/swift-{{ .Full }}-RELEASE"
return s.Language.Enabled()
}
-35
View File
@@ -1,35 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSwift(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{Case: "Swift 5.5.3", ExpectedString: "5.5.3", Version: "Swift version 5.5.3 (swift-5.5.3-RELEASE)"},
{Case: "Swift 5.5.3 on Windows", ExpectedString: "5.5.3", Version: "compnerd.org Swift version 5.5.3 (swift-5.5.3-RELEASE)"},
{Case: "Swift 5.5.3 on Mac", ExpectedString: "5.5.3", Version: "Apple Swift version 5.5.3 (swift-5.5.3-RELEASE)"},
{Case: "Swift 5.5", ExpectedString: "5.5", Version: "Swift version 5.5 (swift-5.5-RELEASE)"},
{Case: "Swift 5.6-dev", ExpectedString: "5.6-dev", Version: "Swift version 5.6-dev (LLVM 62b900d3d0d5be9, Swift ce64fe8867792d4)"},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "swift",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.swift",
}
env, props := getMockedLanguageEnv(params)
s := &Swift{}
s.Init(props, env)
assert.True(t, s.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, s.Template(), s), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
-23
View File
@@ -1,23 +0,0 @@
package segments
type V struct {
Language
}
func (v *V) Template() string {
return languageTemplate
}
func (v *V) Enabled() bool {
v.extensions = []string{"*.v"}
v.tooling = map[string]*cmd{
"v": {
executable: "v",
args: []string{"--version"},
regex: `V (?P<version>(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)) [a-f0-9]+`,
},
}
v.defaultTooling = []string{"v"}
return v.Language.Enabled()
}
-46
View File
@@ -1,46 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestV(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{
Case: "V 0.4.9",
ExpectedString: "0.4.9",
Version: "V 0.4.9 b487986",
},
{
Case: "V 0.4.8",
ExpectedString: "0.4.8",
Version: "V 0.4.8 a123456",
},
{
Case: "V 0.4.7",
ExpectedString: "0.4.7",
Version: "V 0.4.7 f789012",
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "v",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.v",
}
env, props := getMockedLanguageEnv(params)
v := &V{}
v.Init(props, env)
assert.True(t, v.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, v.Template(), v), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
-26
View File
@@ -1,26 +0,0 @@
package segments
type Vala struct {
Language
}
func (v *Vala) Template() string {
return languageTemplate
}
func (v *Vala) Enabled() bool {
const valaToolName = "vala"
v.extensions = []string{"*.vala"}
v.tooling = map[string]*cmd{
valaToolName: {
executable: valaToolName,
args: []string{versionFlagArg},
regex: `Vala (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+).(?P<patch>[0-9]+)))`,
},
}
v.defaultTooling = []string{valaToolName}
v.versionURLTemplate = "https://gitlab.gnome.org/GNOME/vala/raw/{{ .Major }}.{{ .Minor }}/NEWS"
return v.Language.Enabled()
}
-35
View File
@@ -1,35 +0,0 @@
package segments
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
func TestVala(t *testing.T) {
cases := []struct {
Case string
ExpectedString string
Version string
}{
{
Case: "vala 0.48.17",
ExpectedString: "0.48.17",
Version: "Vala 0.48.17",
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "vala",
versionParam: "--version",
versionOutput: tc.Version,
extension: "*.vala",
}
env, props := getMockedLanguageEnv(params)
v := &Vala{}
v.Init(props, env)
assert.True(t, v.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
assert.Equal(t, tc.ExpectedString, renderTemplate(env, v.Template(), v), fmt.Sprintf("Failed in case: %s", tc.Case))
}
}
-32
View File
@@ -1,32 +0,0 @@
package segments
type Zig struct {
Language
}
func (zig *Zig) Template() string {
return languageTemplate
}
func (zig *Zig) Enabled() bool {
const zigToolName = "zig"
zig.extensions = []string{"*.zig", "*.zon"}
zig.projectFiles = []string{"build.zig"}
zig.tooling = map[string]*cmd{
zigToolName: {
executable: zigToolName,
args: []string{versionArg},
regex: `(?P<version>(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)`, //nolint:lll
},
}
zig.defaultTooling = []string{zigToolName}
zig.versionURLTemplate = "https://ziglang.org/download/{{ .Major }}.{{ .Minor }}.{{ .Patch }}/release-notes.html"
return zig.Language.Enabled()
}
func (zig *Zig) InProjectDir() bool {
return zig.projectRoot != nil
}
-74
View File
@@ -1,74 +0,0 @@
package segments
import (
"errors"
"testing"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/stretchr/testify/assert"
)
func TestZig(t *testing.T) {
cases := []struct {
Case string
Version string
ExpectedString string
ExpectedURL string
InProjectDir bool
}{
{
Case: "zig 0.13.0 - not in project dir",
Version: "0.13.0",
InProjectDir: false,
ExpectedString: "0.13.0",
ExpectedURL: "https://ziglang.org/download/0.13.0/release-notes.html",
},
{
Case: "zig 0.12.0-dev.2063+804cee3b9 - not in project dir",
Version: "0.12.0-dev.2063+804cee3b9",
InProjectDir: false,
ExpectedString: "0.12.0-dev.2063+804cee3b9",
ExpectedURL: "https://ziglang.org/download/0.12.0/release-notes.html",
},
{
Case: "zig 0.13.0 - in project dir",
Version: "0.13.0",
InProjectDir: true,
ExpectedString: "0.13.0",
ExpectedURL: "https://ziglang.org/download/0.13.0/release-notes.html",
},
{
Case: "zig 0.12.0-dev.2063+804cee3b9 - in project dir",
Version: "0.12.0-dev.2063+804cee3b9",
InProjectDir: true,
ExpectedString: "0.12.0-dev.2063+804cee3b9",
ExpectedURL: "https://ziglang.org/download/0.12.0/release-notes.html",
},
}
for _, tc := range cases {
params := &mockedLanguageParams{
cmd: "zig",
versionParam: "version",
versionOutput: tc.Version,
extension: "*.zig",
}
env, props := getMockedLanguageEnv(params)
dummyDir := &runtime.FileInfo{}
if tc.InProjectDir {
env.On("HasParentFilePath", "build.zig", false).Return(dummyDir, nil)
} else {
env.On("HasParentFilePath", "build.zig", false).Return(dummyDir, errors.New("build.zig not found"))
}
zig := &Zig{}
zig.Init(props, env)
assert.True(t, zig.Enabled(), tc.Case)
assert.Equal(t, tc.ExpectedString, renderTemplate(env, zig.Template(), zig), tc.Case)
assert.Equal(t, tc.ExpectedURL, renderTemplate(env, zig.URL, zig), tc.Case)
assert.Equal(t, tc.InProjectDir, zig.InProjectDir(), tc.Case)
}
}