mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2024-11-09 20:44:03 -08:00
feat: dotnet segment for .NET SDK display
New segment for .NET SDK version (or unsupported version) display. Includes update for handling command execution errors so segments can act differently based on exit codes. Using a custom error type to make it testable rather than passing the OS error directly to the segment.
This commit is contained in:
parent
23233bc383
commit
5844faa54d
31
docs/docs/segment-dotnet.md
Normal file
31
docs/docs/segment-dotnet.md
Normal file
|
@ -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)
|
|
@ -17,6 +17,7 @@ module.exports = {
|
|||
"az",
|
||||
"battery",
|
||||
"command",
|
||||
"dotnet",
|
||||
"environment",
|
||||
"exit",
|
||||
"git",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
58
segment_dotnet.go
Normal file
58
segment_dotnet.go
Normal file
|
@ -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
|
||||
}
|
81
segment_dotnet_test.go
Normal file
81
segment_dotnet_test.go
Normal file
|
@ -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())
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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, " ")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue