diff --git a/docs/docs/segment-dotnet.md b/docs/docs/segment-dotnet.md new file mode 100644 index 00000000..2ce5920f --- /dev/null +++ b/docs/docs/segment-dotnet.md @@ -0,0 +1,31 @@ +--- +id: dotnet +title: Dotnet +sidebar_label: Dotnet +--- + +## What + +Display the currently active .NET SDK version. + +## Sample Configuration + +```json +{ + "type": "dotnet", + "style": "powerline", + "powerline_symbol": "", + "foreground": "#000000", + "background": "#00ffff", + "properties": { + "prefix": " \uE77F " + } +} +``` + +## Properties + +- display_version: `boolean` - display the active version or not; useful if all you need is an icon indicating `dotnet` + is present - defaults to `true` +- 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) diff --git a/docs/sidebars.js b/docs/sidebars.js index ac2ad992..dafa34ad 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -17,6 +17,7 @@ module.exports = { "az", "battery", "command", + "dotnet", "environment", "exit", "git", diff --git a/environment.go b/environment.go index 678999a4..68446b09 100755 --- a/environment.go +++ b/environment.go @@ -1,6 +1,8 @@ package main import ( + "errors" + "fmt" "io/ioutil" "log" "os" @@ -26,7 +28,7 @@ type environmentInfo interface { getHostName() (string, error) getRuntimeGOOS() string hasCommand(command string) bool - runCommand(command string, args ...string) string + runCommand(command string, args ...string) (string, error) runShellCommand(shell string, command string) string lastErrorCode() int getArgs() *args @@ -39,6 +41,14 @@ type environment struct { cwd string } +type commandError struct { + exitCode int +} + +func (e *commandError) Error() string { + return fmt.Sprintf("%d", e.exitCode) +} + func (env *environment) getenv(key string) string { return os.Getenv(key) } @@ -110,12 +120,18 @@ func (env *environment) getRuntimeGOOS() string { return runtime.GOOS } -func (env *environment) runCommand(command string, args ...string) string { +func (env *environment) runCommand(command string, args ...string) (string, error) { out, err := exec.Command(command, args...).Output() - if err != nil { - return "" + + var exerr *exec.ExitError + if errors.As(err, &exerr) { + return "", &commandError{exitCode: exerr.ExitCode()} } - return strings.TrimSpace(string(out)) + if err != nil { + return "", err + } + + return strings.TrimSpace(string(out)), nil } func (env *environment) runShellCommand(shell string, command string) string { diff --git a/properties.go b/properties.go index 2f35692d..c8671a39 100644 --- a/properties.go +++ b/properties.go @@ -20,6 +20,8 @@ const ( ColorBackground Property = "color_background" //IgnoreFolders folders to ignore and not run the segment logic IgnoreFolders Property = "ignore_folders" + //DisplayVersion show the version number or not + DisplayVersion Property = "display_version" ) type properties struct { diff --git a/segment.go b/segment.go index 91fb77bc..7cddd85d 100644 --- a/segment.go +++ b/segment.go @@ -67,6 +67,8 @@ const ( Az SegmentType = "az" //Kubectl writes the Kubernetes context we're currently in Kubectl SegmentType = "kubectl" + //Dotnet writes which dotnet version is currently active + Dotnet SegmentType = "dotnet" //Powerline writes it Powerline style Powerline SegmentStyle = "powerline" //Plain writes it without ornaments @@ -123,6 +125,7 @@ func (segment *Segment) mapSegmentWithWriter(env environmentInfo) error { EnvVar: &envvar{}, Az: &az{}, Kubectl: &kubectl{}, + Dotnet: &dotnet{}, } if writer, ok := functions[segment.Type]; ok { props := &properties{ diff --git a/segment_az.go b/segment_az.go index 3735a08c..ce49c289 100644 --- a/segment_az.go +++ b/segment_az.go @@ -40,7 +40,7 @@ func (a *az) enabled() bool { return false } - output := a.env.runCommand("az", "account", "show", "--query=[name,id]", "-o=tsv") + output, _ := a.env.runCommand("az", "account", "show", "--query=[name,id]", "-o=tsv") if output == "" { return false } diff --git a/segment_dotnet.go b/segment_dotnet.go new file mode 100644 index 00000000..e5a8e4e0 --- /dev/null +++ b/segment_dotnet.go @@ -0,0 +1,58 @@ +package main + +import ( + "errors" +) + +type dotnet struct { + props *properties + env environmentInfo + activeVersion string + unsupportedVersion bool +} + +const ( + //UnsupportedDotnetVersionIcon is displayed when the dotnet version in + //the current folder isn't supported by the installed dotnet SDK set. + UnsupportedDotnetVersionIcon Property = "unsupported_version_icon" +) + +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 + } + + // 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 + } + + return false +} diff --git a/segment_dotnet_test.go b/segment_dotnet_test.go new file mode 100644 index 00000000..9172cdad --- /dev/null +++ b/segment_dotnet_test.go @@ -0,0 +1,81 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type dotnetArgs struct { + enabled bool + version string + unsupported bool + unsupportedIcon string + displayVersion bool +} + +func bootStrapDotnetTest(args *dotnetArgs) *dotnet { + env := new(MockedEnvironment) + env.On("hasCommand", "dotnet").Return(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) + } + props := &properties{ + values: map[Property]interface{}{ + DisplayVersion: args.displayVersion, + UnsupportedDotnetVersionIcon: args.unsupportedIcon, + }, + } + a := &dotnet{ + env: env, + props: props, + } + return a +} + +func TestEnabledDotnetNotFound(t *testing.T) { + args := &dotnetArgs{ + enabled: false, + } + dotnet := bootStrapDotnetTest(args) + assert.False(t, dotnet.enabled()) +} + +func TestDotnetVersionNotDisplayed(t *testing.T) { + args := &dotnetArgs{ + enabled: true, + displayVersion: false, + version: "3.1.402", + } + dotnet := bootStrapDotnetTest(args) + assert.True(t, dotnet.enabled()) + assert.Equal(t, "", dotnet.string()) +} + +func TestDotnetVersionDisplayed(t *testing.T) { + expected := "3.1.402" + args := &dotnetArgs{ + enabled: true, + displayVersion: true, + version: expected, + } + dotnet := bootStrapDotnetTest(args) + assert.True(t, dotnet.enabled()) + assert.Equal(t, expected, dotnet.string()) +} + +func TestDotnetVersionUnsupported(t *testing.T) { + expected := "x" + args := &dotnetArgs{ + enabled: true, + displayVersion: true, + unsupported: true, + unsupportedIcon: expected, + } + dotnet := bootStrapDotnetTest(args) + assert.True(t, dotnet.enabled()) + assert.Equal(t, expected, dotnet.string()) +} diff --git a/segment_git.go b/segment_git.go index 017f3419..084337bb 100755 --- a/segment_git.go +++ b/segment_git.go @@ -101,7 +101,7 @@ func (g *git) enabled() bool { if !g.env.hasCommand("git") { return false } - output := g.env.runCommand("git", "rev-parse", "--is-inside-work-tree") + output, _ := g.env.runCommand("git", "rev-parse", "--is-inside-work-tree") return output == "true" } @@ -183,7 +183,8 @@ func (g *git) setGitStatus() { func (g *git) getGitCommandOutput(args ...string) string { args = append([]string{"-c", "core.quotepath=false", "-c", "color.status=false"}, args...) - return g.env.runCommand("git", args...) + val, _ := g.env.runCommand("git", args...) + return val } func (g *git) getGitHEADContext(ref string) string { diff --git a/segment_git_test.go b/segment_git_test.go index 87fae49e..9844c906 100755 --- a/segment_git_test.go +++ b/segment_git_test.go @@ -18,7 +18,7 @@ func TestEnabledGitNotFound(t *testing.T) { func TestEnabledInWorkingDirectory(t *testing.T) { env := new(MockedEnvironment) env.On("hasCommand", "git").Return(true) - env.On("runCommand", "git", []string{"rev-parse", "--is-inside-work-tree"}).Return("true") + env.On("runCommand", "git", []string{"rev-parse", "--is-inside-work-tree"}).Return("true", nil) g := &git{ env: env, } @@ -30,7 +30,7 @@ func TestGetGitOutputForCommand(t *testing.T) { commandArgs := []string{"symbolic-ref", "--short", "HEAD"} want := "je suis le output" env := new(MockedEnvironment) - env.On("runCommand", "git", append(args, commandArgs...)).Return(want) + env.On("runCommand", "git", append(args, commandArgs...)).Return(want, nil) g := &git{ env: env, } @@ -70,12 +70,12 @@ func setupHEADContextEnv(context *detachedContext) *git { env.On("getFileContent", "/.git/MERGE_HEAD").Return(context.mergeHEAD) env.On("hasFiles", "/.git/CHERRY_PICK_HEAD").Return(context.cherryPick) env.On("hasFiles", "/.git/MERGE_HEAD").Return(context.merge) - env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "rev-parse", "--short", "HEAD"}).Return(context.currentCommit) - env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "describe", "--tags", "--exact-match"}).Return(context.tagName) - env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "name-rev", "--name-only", "--exclude=tags/*", context.origin}).Return(context.origin) - env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "name-rev", "--name-only", "--exclude=tags/*", context.onto}).Return(context.onto) - env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "name-rev", "--name-only", "--exclude=tags/*", context.cherryPickSHA}).Return(context.cherryPickSHA) - env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "name-rev", "--name-only", "--exclude=tags/*", context.mergeHEAD}).Return(context.mergeHEAD) + env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "rev-parse", "--short", "HEAD"}).Return(context.currentCommit, nil) + env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "describe", "--tags", "--exact-match"}).Return(context.tagName, nil) + env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "name-rev", "--name-only", "--exclude=tags/*", context.origin}).Return(context.origin, nil) + env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "name-rev", "--name-only", "--exclude=tags/*", context.onto}).Return(context.onto, nil) + env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "name-rev", "--name-only", "--exclude=tags/*", context.cherryPickSHA}).Return(context.cherryPickSHA, nil) + env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "name-rev", "--name-only", "--exclude=tags/*", context.mergeHEAD}).Return(context.mergeHEAD, nil) g := &git{ env: env, repo: &gitRepo{ @@ -200,7 +200,7 @@ func TestGetGitHEADContextMergeTag(t *testing.T) { func TestGetStashContextZeroEntries(t *testing.T) { want := "" env := new(MockedEnvironment) - env.On("runCommand", "git", []string{"-c", "core.quotepath=false", "-c", "color.status=false", "rev-list", "--walk-reflogs", "--count", "refs/stash"}).Return("") + 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, } @@ -211,7 +211,7 @@ func TestGetStashContextZeroEntries(t *testing.T) { func TestGetStashContextMultipleEntries(t *testing.T) { want := "2" 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") + 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, } diff --git a/segment_kubectl.go b/segment_kubectl.go index 1ea71288..cbe068fb 100644 --- a/segment_kubectl.go +++ b/segment_kubectl.go @@ -19,6 +19,6 @@ func (k *kubectl) enabled() bool { if !k.env.hasCommand("kubectl") { return false } - k.contextName = k.env.runCommand("kubectl", "config", "current-context") + k.contextName, _ = k.env.runCommand("kubectl", "config", "current-context") return true } diff --git a/segment_kubectl_test.go b/segment_kubectl_test.go index 74fe2ca6..37271841 100755 --- a/segment_kubectl_test.go +++ b/segment_kubectl_test.go @@ -14,7 +14,7 @@ type kubectlArgs struct { func bootStrapKubectlTest(args *kubectlArgs) *kubectl { env := new(MockedEnvironment) env.On("hasCommand", "kubectl").Return(args.enabled) - env.On("runCommand", "kubectl", []string{"config", "current-context"}).Return(args.contextName) + env.On("runCommand", "kubectl", []string{"config", "current-context"}).Return(args.contextName, nil) k := &kubectl{ env: env, props: &properties{}, diff --git a/segment_node.go b/segment_node.go index b4b7309c..251985b3 100644 --- a/segment_node.go +++ b/segment_node.go @@ -6,11 +6,6 @@ type node struct { nodeVersion string } -const ( - //DisplayVersion show the version number or not - DisplayVersion Property = "display_version" -) - func (n *node) string() string { if n.props.getBool(DisplayVersion, true) { return n.nodeVersion @@ -30,6 +25,6 @@ func (n *node) enabled() bool { if !n.env.hasCommand("node") { return false } - n.nodeVersion = n.env.runCommand("node", "--version") + n.nodeVersion, _ = n.env.runCommand("node", "--version") return true } diff --git a/segment_node_test.go b/segment_node_test.go index 30d911ac..cea9d6a3 100755 --- a/segment_node_test.go +++ b/segment_node_test.go @@ -17,7 +17,7 @@ type nodeArgs struct { func bootStrapNodeTest(args *nodeArgs) *node { env := new(MockedEnvironment) env.On("hasCommand", "node").Return(args.enabled) - env.On("runCommand", "node", []string{"--version"}).Return(args.nodeVersion) + env.On("runCommand", "node", []string{"--version"}).Return(args.nodeVersion, nil) env.On("hasFiles", "*.js").Return(args.hasJS) env.On("hasFiles", "*.ts").Return(args.hasTS) props := &properties{ diff --git a/segment_path_test.go b/segment_path_test.go index 60e6098c..a1ec33e9 100755 --- a/segment_path_test.go +++ b/segment_path_test.go @@ -68,9 +68,9 @@ func (env *MockedEnvironment) hasCommand(command string) bool { return args.Bool(0) } -func (env *MockedEnvironment) runCommand(command string, args ...string) string { +func (env *MockedEnvironment) runCommand(command string, args ...string) (string, error) { arguments := env.Called(command, args) - return arguments.String(0) + return arguments.String(0), arguments.Error(1) } func (env *MockedEnvironment) runShellCommand(shell string, command string) string { diff --git a/segment_python.go b/segment_python.go index 24110907..ba87d256 100644 --- a/segment_python.go +++ b/segment_python.go @@ -38,7 +38,7 @@ func (p *python) enabled() bool { "python", } for index, python := range pythonVersions { - version := p.env.runCommand(python, "--version") + version, _ := p.env.runCommand(python, "--version") if version != "" { rawVersion := strings.TrimLeft(version, "Python") p.pythonVersion = strings.Trim(rawVersion, " ") diff --git a/segment_python_test.go b/segment_python_test.go index 966b5016..e5de9b71 100755 --- a/segment_python_test.go +++ b/segment_python_test.go @@ -15,8 +15,8 @@ type pythonArgs struct { pathSeparator string pythonVersion string python3Version string - hasPyFiles bool - hasNotebookFiles bool + hasPyFiles bool + hasNotebookFiles bool } func newPythonArgs() *pythonArgs { @@ -37,8 +37,8 @@ func bootStrapPythonTest(args *pythonArgs) *python { env := new(MockedEnvironment) env.On("hasFiles", "*.py").Return(args.hasPyFiles) env.On("hasFiles", "*.ipynb").Return(args.hasNotebookFiles) - env.On("runCommand", "python", []string{"--version"}).Return(args.pythonVersion) - env.On("runCommand", "python3", []string{"--version"}).Return(args.python3Version) + env.On("runCommand", "python", []string{"--version"}).Return(args.pythonVersion, nil) + env.On("runCommand", "python3", []string{"--version"}).Return(args.python3Version, nil) env.On("getenv", "VIRTUAL_ENV").Return(args.virtualEnvName) env.On("getenv", "CONDA_ENV_PATH").Return(args.condaEnvName) env.On("getenv", "CONDA_DEFAULT_ENV").Return(args.condaDefaultName) diff --git a/segment_spotify.go b/segment_spotify.go index 442525da..3c9aa495 100644 --- a/segment_spotify.go +++ b/segment_spotify.go @@ -61,5 +61,6 @@ func (s *spotify) init(props *properties, env environmentInfo) { } func (s *spotify) runAppleScriptCommand(command string) string { - return s.env.runCommand("osascript", "-e", command) + val, _ := s.env.runCommand("osascript", "-e", command) + return val }