fix: don't check language version in Enabled()

fix: align dotnet segment with other languages
feat: missing command text + json schema updated
chore: doc updated
perf: cache executable path
chore: not supported version icon updated(previus one was unreadable)
This commit is contained in:
lnu 2020-12-28 08:33:58 +01:00 committed by Jan De Dobbeleer
parent 07ac2c35cd
commit abfbb27765
22 changed files with 216 additions and 104 deletions

View file

@ -27,5 +27,6 @@ Display the currently active .NET SDK version.
- display_version: `boolean` - display the active version or not; useful if all you need is an icon indicating `dotnet`
is present - defaults to `true`
- missing_command_text: `string` - text to display when the command is missing - default to ``
- unsupported_version_icon: `string` - text/icon that is displayed when the active .NET SDK version (e.g., one specified
by `global.json`) is not installed/supported - defaults to `\u2327` (X in a rectangle box)
by `global.json`) is not installed/supported - defaults to `\uf071` (X in a rectangle box)

View file

@ -26,6 +26,7 @@ Display the currently active golang version when a folder contains `.go` files.
## Properties
- display_version: `boolean` - display the golang version - defaults to `true`
- missing_command_text: `string` - text to display when the command is missing - default to ``
- display_mode: `string` - determines when the segment is displayed
- `always`: The segment is always displayed
- `context`: The segment is only displayed when *.go or go.mod files are present (default)

View file

@ -26,6 +26,7 @@ Display the currently active julia version when a folder contains `.jl` files.
## Properties
- display_version: `boolean` - display the julia version - defaults to `true`
- missing_command_text: `string` - text to display when the command is missing - default to ``
- display_mode: `string` - determines when the segment is displayed
- `always`: The segment is always displayed
- `context`: The segment is only displayed when *.jl files are present (default)

View file

@ -26,6 +26,7 @@ Display the currently active node version when a folder contains `.js` or `.ts`
## Properties
- display_version: `boolean` - display the node version - defaults to `true`
- missing_command_text: `string` - text to display when the command is missing - default to ``
- display_mode: `string` - determines when the segment is displayed
- `always`: The segment is always displayed
- `context`: The segment is only displayed when *.js, *.ts or package.json files are present (default)

View file

@ -27,6 +27,8 @@ Supports conda, virtualenv and pyenv.
## Properties
- display_virtual_env: `boolean` - show the name of the virtualenv or not - defaults to `true`
- display_version: `boolean` - display the python version - defaults to `true`
- missing_command_text: `string` - text to display when the command is missing - default to ``
- display_mode: `string` - determines when the segment is displayed
- `always`: The segment is always displayed
- `context`: The segment is only displayed when *.py or *.ipynb files are present (default)

View file

@ -39,7 +39,7 @@ type environmentInfo interface {
getHostName() (string, error)
getRuntimeGOOS() string
getPlatform() string
hasCommand(command string) bool
hasCommand(command string) (string, bool)
runCommand(command string, args ...string) (string, error)
runShellCommand(shell, command string) string
lastErrorCode() int
@ -221,9 +221,9 @@ func (env *environment) runShellCommand(shell, command string) string {
return strings.TrimSpace(string(out))
}
func (env *environment) hasCommand(command string) bool {
_, err := exec.LookPath(command)
return err == nil
func (env *environment) hasCommand(command string) (string, bool) {
path, err := exec.LookPath(command)
return path, err == nil
}
func (env *environment) lastErrorCode() int {

View file

@ -36,11 +36,12 @@ func (a *az) init(props *properties, env environmentInfo) {
}
func (a *az) enabled() bool {
if (!a.idEnabled() && !a.nameEnabled()) || !a.env.hasCommand("az") {
commandPath, commandExists := a.env.hasCommand("az")
if (!a.idEnabled() && !a.nameEnabled()) || !commandExists {
return false
}
output, _ := a.env.runCommand("az", "account", "show", "--query=[name,id]", "-o=tsv")
output, _ := a.env.runCommand(commandPath, "account", "show", "--query=[name,id]", "-o=tsv")
if output == "" {
return false
}

View file

@ -18,7 +18,7 @@ type azArgs struct {
func bootStrapAzTest(args *azArgs) *az {
env := new(MockedEnvironment)
env.On("hasCommand", "az").Return(args.enabled)
env.On("hasCommand", "az").Return("az", args.enabled)
env.On("runCommand", "az", []string{"account", "show", "--query=[name,id]", "-o=tsv"}).Return(fmt.Sprintf("%s\n%s\n", args.subscriptionName, args.subscriptionID), nil)
props := &properties{
values: map[Property]interface{}{

View file

@ -17,7 +17,8 @@ const (
func (c *command) enabled() bool {
shell := c.props.getString(ExecutableShell, "bash")
if !c.env.hasCommand(shell) {
shell, commandExists := c.env.hasCommand(shell)
if !commandExists {
return false
}
command := c.props.getString(Command, "echo no command specified")

View file

@ -1,14 +1,7 @@
package main
import (
"errors"
)
type dotnet struct {
props *properties
env environmentInfo
activeVersion string
unsupportedVersion bool
language *language
}
const (
@ -18,41 +11,29 @@ const (
)
func (d *dotnet) string() string {
if d.unsupportedVersion {
return d.props.getString(UnsupportedDotnetVersionIcon, "\u2327")
}
if d.props.getBool(DisplayVersion, true) {
return d.activeVersion
}
return ""
}
func (d *dotnet) init(props *properties, env environmentInfo) {
d.props = props
d.env = env
}
func (d *dotnet) enabled() bool {
if !d.env.hasCommand("dotnet") {
return false
}
output, err := d.env.runCommand("dotnet", "--version")
if err == nil {
d.activeVersion = output
return true
}
version := d.language.string()
// Exit code 145 is a special indicator that dotnet
// ran, but the current project config settings specify
// use of an SDK that isn't installed.
var exerr *commandError
if errors.As(err, &exerr) && exerr.exitCode == 145 {
d.unsupportedVersion = true
return true
if d.language.exitCode == 145 {
return d.language.props.getString(UnsupportedDotnetVersionIcon, "\uf071 ")
}
return false
return version
}
func (d *dotnet) init(props *properties, env environmentInfo) {
d.language = &language{
env: env,
props: props,
commands: []string{"dotnet"},
versionParam: "--version",
extensions: []string{"*.cs", "*.vb", "*.sln", "*.csproj", "*.vbproj"},
versionRegex: `(?P<version>[0-9]+.[0-9]+.[0-9]+)`,
}
}
func (d *dotnet) enabled() bool {
return d.language.enabled()
}

View file

@ -16,24 +16,25 @@ type dotnetArgs struct {
func bootStrapDotnetTest(args *dotnetArgs) *dotnet {
env := new(MockedEnvironment)
env.On("hasCommand", "dotnet").Return(args.enabled)
env.On("hasCommand", "dotnet").Return("dotnet", args.enabled)
if args.unsupported {
err := &commandError{exitCode: 145}
env.On("runCommand", "dotnet", []string{"--version"}).Return("", err)
} else {
env.On("runCommand", "dotnet", []string{"--version"}).Return(args.version, nil)
}
env.On("hasFiles", "*.cs").Return(true)
env.On("getPathSeperator", nil).Return("")
props := &properties{
values: map[Property]interface{}{
DisplayVersion: args.displayVersion,
UnsupportedDotnetVersionIcon: args.unsupportedIcon,
},
}
a := &dotnet{
env: env,
props: props,
}
return a
dotnet := &dotnet{}
dotnet.init(props, env)
return dotnet
}
func TestEnabledDotnetNotFound(t *testing.T) {
@ -41,7 +42,7 @@ func TestEnabledDotnetNotFound(t *testing.T) {
enabled: false,
}
dotnet := bootStrapDotnetTest(args)
assert.False(t, dotnet.enabled())
assert.True(t, dotnet.enabled())
}
func TestDotnetVersionNotDisplayed(t *testing.T) {

View file

@ -47,9 +47,10 @@ func (s *gitStatus) string(prefix, color string) string {
}
type git struct {
props *properties
env environmentInfo
repo *gitRepo
props *properties
env environmentInfo
repo *gitRepo
commandPath string
}
const (
@ -114,10 +115,12 @@ const (
)
func (g *git) enabled() bool {
if !g.env.hasCommand("git") {
commandPath, commandExists := g.env.hasCommand("git")
if !commandExists {
return false
}
output, _ := g.env.runCommand("git", "rev-parse", "--is-inside-work-tree")
g.commandPath = commandPath
output, _ := g.env.runCommand(g.commandPath, "rev-parse", "--is-inside-work-tree")
return output == "true"
}
@ -235,7 +238,7 @@ func (g *git) getStatusColor(defaultValue string) string {
func (g *git) getGitCommandOutput(args ...string) string {
args = append([]string{"-c", "core.quotepath=false", "-c", "color.status=false"}, args...)
val, _ := g.env.runCommand("git", args...)
val, _ := g.env.runCommand(g.commandPath, args...)
return val
}

View file

@ -12,7 +12,7 @@ const (
func TestEnabledGitNotFound(t *testing.T) {
env := new(MockedEnvironment)
env.On("hasCommand", "git").Return(false)
env.On("hasCommand", "git").Return("", false)
g := &git{
env: env,
}
@ -21,7 +21,7 @@ func TestEnabledGitNotFound(t *testing.T) {
func TestEnabledInWorkingDirectory(t *testing.T) {
env := new(MockedEnvironment)
env.On("hasCommand", "git").Return(true)
env.On("hasCommand", "git").Return("git", true)
env.On("runCommand", "git", []string{"rev-parse", "--is-inside-work-tree"}).Return("true", nil)
g := &git{
env: env,
@ -36,7 +36,8 @@ func TestGetGitOutputForCommand(t *testing.T) {
env := new(MockedEnvironment)
env.On("runCommand", "git", append(args, commandArgs...)).Return(want, nil)
g := &git{
env: env,
env: env,
commandPath: "git",
}
got := g.getGitCommandOutput(commandArgs...)
assert.Equal(t, want, got)
@ -85,6 +86,7 @@ func setupHEADContextEnv(context *detachedContext) *git {
repo: &gitRepo{
root: "",
},
commandPath: "git",
}
return g
}
@ -211,7 +213,8 @@ func TestGetStashContextZeroEntries(t *testing.T) {
env := new(MockedEnvironment)
env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "rev-list", "--walk-reflogs", "--count", "refs/stash"}).Return("", nil)
g := &git{
env: env,
env: env,
commandPath: "git",
}
got := g.getStashContext()
assert.Equal(t, want, got)
@ -222,7 +225,8 @@ func TestGetStashContextMultipleEntries(t *testing.T) {
env := new(MockedEnvironment)
env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "rev-list", "--walk-reflogs", "--count", "refs/stash"}).Return("2", nil)
g := &git{
env: env,
env: env,
commandPath: "git",
}
got := g.getStashContext()
assert.Equal(t, want, got)
@ -390,7 +394,8 @@ func bootstrapUpstreamTest(upstream string) *git {
repo: &gitRepo{
upstream: "origin/main",
},
props: props,
props: props,
commandPath: "git",
}
return g
}

View file

@ -16,9 +16,10 @@ func (k *kubectl) init(props *properties, env environmentInfo) {
}
func (k *kubectl) enabled() bool {
if !k.env.hasCommand("kubectl") {
commandPath, commandExists := k.env.hasCommand("kubectl")
if !commandExists {
return false
}
k.contextName, _ = k.env.runCommand("kubectl", "config", "current-context")
k.contextName, _ = k.env.runCommand(commandPath, "config", "current-context")
return k.contextName != ""
}

View file

@ -13,7 +13,7 @@ type kubectlArgs struct {
func bootStrapKubectlTest(args *kubectlArgs) *kubectl {
env := new(MockedEnvironment)
env.On("hasCommand", "kubectl").Return(args.enabled)
env.On("hasCommand", "kubectl").Return("kubectl", args.enabled)
env.On("runCommand", "kubectl", []string{"config", "current-context"}).Return(args.contextName, nil)
k := &kubectl{
env: env,

View file

@ -1,13 +1,17 @@
package main
import "errors"
type language struct {
props *properties
env environmentInfo
extensions []string
commands []string
executable string
versionParam string
versionRegex string
version string
exitCode int
}
const (
@ -19,10 +23,20 @@ const (
DisplayModeContext string = "context"
// DisplayModeNever hides the segment
DisplayModeNever string = "never"
// MissingCommandProperty sets the text to display when the command is not present in the system
MissingCommandTextProperty Property = "missing_command_text"
// MissingCommand displays empty string by default
MissingCommandText string = ""
)
func (l *language) string() string {
if l.props.getBool(DisplayVersion, true) {
// check if one of the defined commands exists in the system
if !l.hasCommand() {
return l.props.getString(MissingCommandTextProperty, MissingCommandText)
}
// call getVersion if displayVersion set in config
if l.props.getBool(DisplayVersion, true) && l.getVersion() {
return l.version
}
return ""
@ -31,20 +45,20 @@ func (l *language) string() string {
func (l *language) enabled() bool {
displayMode := l.props.getString(DisplayModeProperty, DisplayModeContext)
displayVersion := l.props.getBool(DisplayVersion, true)
hasVersion := l.getVersion()
switch displayMode {
case DisplayModeAlways:
return (hasVersion || !displayVersion)
return (!displayVersion || l.hasCommand())
case DisplayModeNever:
return false
case DisplayModeContext:
fallthrough
default:
return l.isInContext() && (hasVersion || !displayVersion)
return l.isInContext() && (!displayVersion || l.hasCommand())
}
}
// isInContext will return true at least one file matching the extensions is found
func (l *language) isInContext() bool {
for i, extension := range l.extensions {
if l.env.hasFiles(extension) {
@ -58,20 +72,33 @@ func (l *language) isInContext() bool {
return true
}
// getVersion returns the version an exit code returned by the exexutable
func (l *language) getVersion() bool {
var executable string
versionInfo, err := l.env.runCommand(l.executable, l.versionParam)
var exerr *commandError
if err == nil {
values := findNamedRegexMatch(l.versionRegex, versionInfo)
l.exitCode = 0
l.version = values["version"]
} else {
errors.As(err, &exerr)
l.exitCode = exerr.exitCode
l.version = ""
}
return true
}
// hasCommand checks if one of the commands exists and set it as executablr
func (l *language) hasCommand() bool {
for i, command := range l.commands {
if l.env.hasCommand(command) {
executable = command
commandPath, commandExists := l.env.hasCommand(command)
if commandExists {
l.executable = commandPath
break
}
if i == len(l.commands)-1 {
return false
}
}
versionInfo, _ := l.env.runCommand(executable, l.versionParam)
values := findNamedRegexMatch(l.versionRegex, versionInfo)
l.version = values["version"]
return true
}

View file

@ -13,15 +13,16 @@ const (
)
type languageArgs struct {
version string
displayVersion bool
displayMode string
extensions []string
enabledExtensions []string
commands []string
enabledCommands []string
versionParam string
versionRegex string
version string
displayVersion bool
displayMode string
extensions []string
enabledExtensions []string
commands []string
enabledCommands []string
versionParam string
versionRegex string
missingCommandText string
}
func (l *languageArgs) hasvalue(value string, list []string) bool {
@ -36,7 +37,7 @@ func (l *languageArgs) hasvalue(value string, list []string) bool {
func bootStrapLanguageTest(args *languageArgs) *language {
env := new(MockedEnvironment)
for _, command := range args.commands {
env.On("hasCommand", command).Return(args.hasvalue(command, args.enabledCommands))
env.On("hasCommand", command).Return(command, args.hasvalue(command, args.enabledCommands))
env.On("runCommand", command, []string{args.versionParam}).Return(args.version, nil)
}
for _, extension := range args.extensions {
@ -48,6 +49,9 @@ func bootStrapLanguageTest(args *languageArgs) *language {
DisplayModeProperty: args.displayMode,
},
}
if args.missingCommandText != "" {
props.values[MissingCommandTextProperty] = args.missingCommandText
}
l := &language{
props: props,
env: env,
@ -59,7 +63,7 @@ func bootStrapLanguageTest(args *languageArgs) *language {
return l
}
func TestLanguageFilesFoundButNoCommandAndVersion(t *testing.T) {
func TestLanguageFilesFoundButNoCommandAndVersionAndDisplayVersion(t *testing.T) {
args := &languageArgs{
commands: []string{"unicorn"},
versionParam: "--version",
@ -71,6 +75,18 @@ func TestLanguageFilesFoundButNoCommandAndVersion(t *testing.T) {
assert.False(t, lang.enabled(), "unicorn is not available")
}
func TestLanguageFilesFoundButNoCommandAndVersionAndDontDisplayVersion(t *testing.T) {
args := &languageArgs{
commands: []string{"unicorn"},
versionParam: "--version",
extensions: []string{uni},
enabledExtensions: []string{uni},
displayVersion: false,
}
lang := bootStrapLanguageTest(args)
assert.True(t, lang.enabled(), "unicorn is not available")
}
func TestLanguageFilesFoundButNoCommandAndNoVersion(t *testing.T) {
args := &languageArgs{
commands: []string{"unicorn"},
@ -172,3 +188,36 @@ func TestLanguageEnabledNoVersion(t *testing.T) {
assert.True(t, lang.enabled())
assert.Equal(t, "", lang.string(), "unicorn is available and uni and corn files are found")
}
func TestLanguageEnabledMissingCommand(t *testing.T) {
args := &languageArgs{
versionParam: "--version",
commands: []string{""},
enabledCommands: []string{"unicorn"},
extensions: []string{uni, corn},
versionRegex: "(?P<version>.*)",
version: universion,
enabledExtensions: []string{uni, corn},
displayVersion: false,
}
lang := bootStrapLanguageTest(args)
assert.True(t, lang.enabled())
assert.Equal(t, "", lang.string(), "unicorn is available and uni and corn files are found")
}
func TestLanguageEnabledMissingCommandCustomText(t *testing.T) {
args := &languageArgs{
versionParam: "--version",
commands: []string{""},
enabledCommands: []string{"unicorn"},
extensions: []string{uni, corn},
versionRegex: "(?P<version>.*)",
version: universion,
enabledExtensions: []string{uni, corn},
displayVersion: false,
missingCommandText: "missing",
}
lang := bootStrapLanguageTest(args)
assert.True(t, lang.enabled())
assert.Equal(t, args.missingCommandText, lang.string(), "unicorn is available and uni and corn files are found")
}

View file

@ -72,9 +72,9 @@ func (env *MockedEnvironment) getPlatform() string {
return args.String(0)
}
func (env *MockedEnvironment) hasCommand(command string) bool {
func (env *MockedEnvironment) hasCommand(command string) (string, bool) {
args := env.Called(command)
return args.Bool(0)
return args.String(0), args.Bool(1)
}
func (env *MockedEnvironment) runCommand(command string, args ...string) (string, error) {

View file

@ -16,7 +16,7 @@ type pythonArgs struct {
func bootStrapPythonTest(args *pythonArgs) *python {
env := new(MockedEnvironment)
env.On("hasCommand", "python").Return(true)
env.On("hasCommand", "python").Return("python", true)
env.On("runCommand", "python", []string{"--version"}).Return("Python 3.8.4", nil)
env.On("hasFiles", "*.py").Return(true)
env.On("getenv", "VIRTUAL_ENV").Return(args.virtualEnvName)
@ -83,6 +83,6 @@ func TestPythonPyEnvWithVersion(t *testing.T) {
}
python := bootStrapPythonTest(args)
assert.True(t, python.enabled())
assert.Equal(t, "3.8.4", python.language.version)
assert.Equal(t, expected, python.string())
assert.Equal(t, "3.8.4", python.language.version)
}

View file

@ -16,9 +16,10 @@ func (tf *terraform) init(props *properties, env environmentInfo) {
}
func (tf *terraform) enabled() bool {
if !tf.env.hasCommand("terraform") || !tf.env.hasFolder(".terraform") {
commandPath, commandExists := tf.env.hasCommand("terraform")
if !commandExists || !tf.env.hasFolder(".terraform") {
return false
}
tf.workspaceName, _ = tf.env.runCommand("terraform", "workspace", "show")
tf.workspaceName, _ = tf.env.runCommand(commandPath, "workspace", "show")
return true
}

View file

@ -14,7 +14,7 @@ type terraformArgs struct {
func bootStrapTerraformTest(args *terraformArgs) *terraform {
env := new(MockedEnvironment)
env.On("hasCommand", "terraform").Return(args.hasTfCommand)
env.On("hasCommand", "terraform").Return("terraform", args.hasTfCommand)
env.On("hasFolder", ".terraform").Return(args.hasTfFolder)
env.On("runCommand", "terraform", []string{"workspace", "show"}).Return(args.workspaceName, nil)
k := &terraform{

View file

@ -17,6 +17,19 @@
"description": "Show or hide the version number",
"default": true
},
"display_mode": {
"type": "string",
"title": "Display Mode",
"description": "Determines whether the segment is displayed always or only if a file matching the extensions are present in the current folder",
"enum": ["always", "context", "never"],
"default": "context"
},
"missing_command_text": {
"type": "string",
"title": "Missing command text",
"description": "The string to display when the command is not available",
"default": ""
},
"block": {
"type": "object",
"description": "https://ohmyposh.dev/docs/configure#block",
@ -357,6 +370,12 @@
},
"display_version": {
"$ref": "#/definitions/display_version"
},
"display_mode": {
"$ref": "#/definitions/display_mode"
},
"missing_command_text": {
"$ref": "#/definitions/missing_command_text"
}
}
}
@ -613,6 +632,12 @@
"properties": {
"display_version": {
"$ref": "#/definitions/display_version"
},
"display_mode": {
"$ref": "#/definitions/display_mode"
},
"missing_command_text": {
"$ref": "#/definitions/missing_command_text"
}
}
}
@ -633,6 +658,12 @@
"properties": {
"display_version": {
"$ref": "#/definitions/display_version"
},
"display_mode": {
"$ref": "#/definitions/display_mode"
},
"missing_command_text": {
"$ref": "#/definitions/missing_command_text"
}
}
}
@ -664,6 +695,12 @@
"properties": {
"display_version": {
"$ref": "#/definitions/display_version"
},
"display_mode": {
"$ref": "#/definitions/display_mode"
},
"missing_command_text": {
"$ref": "#/definitions/missing_command_text"
}
}
}
@ -935,11 +972,10 @@
"default": true
},
"display_mode": {
"type": "string",
"title": "Display Mode",
"description": "Determines whether the segment is displayed always or only if *.py or *.ipynb file are present in the current folder",
"enum": ["always", "context", "never"],
"default": "context"
"$ref": "#/definitions/display_mode"
},
"missing_command_text": {
"$ref": "#/definitions/missing_command_text"
}
}
}