Skip to content

Commit

Permalink
feat(exit): implement pipestatus
Browse files Browse the repository at this point in the history
BREAKING CHANGE: exit segment is now called status segment.

The exit keyword is now deprecated and will be removed in a future
release. Please use the status keyword instead:

```diff
"segments": {
    {
-     "type": "exit"
+     "type": "status"
    }
}
```

Additionally, the status segment configuration has changed to support
$PIPESTATUS. You can include a status template to customize the
rendering of each individual status code (supported in fish, zsh and
bash).

```json
"segments": {
    {
        "type": "status",
        "properties": {
            "status_template": "{{ if gt .Code 0 }}\uf071{{ else }}\uf00c{{ end }}",
            "status_separator": " "
        }
    }
}
```

In case no $PIPESTATUS is available, the status segment will fall back
to the exit code of the last command using the status template
for rendering.

The `{{ .Meaning }}` property has been marked as deprecated and can be
replaced with `{{ reason .Code }}`, allowing it to be reused in
cross segment templates.

resolves #4070
  • Loading branch information
JanDeDobbeleer committed Jul 24, 2023
1 parent 2da3722 commit f47da95
Show file tree
Hide file tree
Showing 109 changed files with 470 additions and 330 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"print",
"transient",
"--shell=pwsh",
"--error=1"
"--status=1"
]
},
{
Expand Down
18 changes: 12 additions & 6 deletions src/cli/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import (
var (
pwd string
pswd string
exitCode int
status int
pipestatus string
timing float64
stackCount int
terminalWidth int
Expand All @@ -22,7 +23,7 @@ var (
command string
shellVersion string
plain bool
noExitCode bool
noStatus bool
)

// printCmd represents the prompt command
Expand Down Expand Up @@ -51,7 +52,8 @@ var printCmd = &cobra.Command{
Config: config,
PWD: pwd,
PSWD: pswd,
ErrorCode: exitCode,
ErrorCode: status,
PipeStatus: pipestatus,
ExecutionTime: timing,
StackCount: stackCount,
TerminalWidth: terminalWidth,
Expand All @@ -61,7 +63,7 @@ var printCmd = &cobra.Command{
Plain: plain,
Primary: args[0] == "primary",
Cleared: cleared,
NoExitCode: noExitCode,
NoExitCode: noStatus,
}

eng := engine.New(flags)
Expand Down Expand Up @@ -95,14 +97,18 @@ func init() { //nolint:gochecknoinits
printCmd.Flags().StringVar(&pswd, "pswd", "", "current working directory (according to pwsh)")
printCmd.Flags().StringVar(&shellName, "shell", "", "the shell to print for")
printCmd.Flags().StringVar(&shellVersion, "shell-version", "", "the shell version")
printCmd.Flags().IntVarP(&exitCode, "error", "e", 0, "last exit code")
printCmd.Flags().IntVar(&status, "status", 0, "last known status code")
printCmd.Flags().BoolVar(&noStatus, "no-status", false, "no valid status code (cancelled or no command yet)")
printCmd.Flags().StringVar(&pipestatus, "pipestatus", "", "the PIPESTATUS array")
printCmd.Flags().Float64Var(&timing, "execution-time", 0, "timing of the last command")
printCmd.Flags().IntVarP(&stackCount, "stack-count", "s", 0, "number of locations on the stack")
printCmd.Flags().IntVarP(&terminalWidth, "terminal-width", "w", 0, "width of the terminal")
printCmd.Flags().StringVar(&command, "command", "", "tooltip command")
printCmd.Flags().BoolVarP(&plain, "plain", "p", false, "plain text output (no ANSI)")
printCmd.Flags().BoolVar(&cleared, "cleared", false, "do we have a clear terminal or not")
printCmd.Flags().BoolVar(&eval, "eval", false, "output the prompt for eval")
printCmd.Flags().BoolVar(&noExitCode, "no-exit-code", false, "no valid exit code (cancelled or no command yet)")
// Deprecated flags
printCmd.Flags().IntVarP(&status, "error", "e", 0, "last exit code")
printCmd.Flags().BoolVar(&noStatus, "no-exit-code", false, "no valid exit code (cancelled or no command yet)")
RootCmd.AddCommand(printCmd)
}
2 changes: 1 addition & 1 deletion src/engine/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func (segment *Segment) migrationOne(env platform.Environment) {
segment.migrateColorOverride("version_mismatch_color", "{{ if .Mismatch }}%s{{ end }}", background)
}
case EXIT:
template := segment.Properties.GetString(segmentTemplate, segment.writer.Template())
template := segment.Properties.GetString(segmentTemplate, "{{ if gt .Code 0 }}\uf00d {{ .Meaning }}{{ else }}\uf42e{{ end }}")
if strings.Contains(template, ".Text") {
template = strings.ReplaceAll(template, ".Text", ".Meaning")
segment.Properties[segmentTemplate] = template
Expand Down
5 changes: 4 additions & 1 deletion src/engine/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/segments"

"github.com/stretchr/testify/assert"
mock2 "github.com/stretchr/testify/mock"
)

const (
Expand Down Expand Up @@ -326,7 +327,9 @@ func TestSegmentTemplateMigration(t *testing.T) {
Type: tc.Type,
Properties: tc.Props,
}
segment.migrationOne(&mock.MockedEnvironment{})
env := &mock.MockedEnvironment{}
env.On("Debug", mock2.Anything).Return(nil)
segment.migrationOne(env)
assert.Equal(t, tc.Expected, segment.Properties[segmentTemplate], tc.Case)
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/engine/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const (

func (e *Engine) Primary() string {
if e.Config.ShellIntegration {
exitCode := e.Env.ErrorCode()
exitCode, _ := e.Env.StatusCodes()
e.write(e.Writer.CommandFinished(exitCode, e.Env.Flags().NoExitCode))
e.write(e.Writer.PromptStart())
}
Expand Down Expand Up @@ -150,7 +150,7 @@ func (e *Engine) ExtraPrompt(promptType ExtraPromptType) string {
}

if promptType == Transient && e.Config.ShellIntegration {
exitCode := e.Env.ErrorCode()
exitCode, _ := e.Env.StatusCodes()
e.write(e.Writer.CommandFinished(exitCode, e.Env.Flags().NoExitCode))
e.write(e.Writer.PromptStart())
}
Expand Down
5 changes: 4 additions & 1 deletion src/engine/segment.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,8 @@ const (
SITECORE SegmentType = "sitecore"
// SPOTIFY writes the SPOTIFY status for Mac
SPOTIFY SegmentType = "spotify"
// STATUS writes the last know command status
STATUS SegmentType = "status"
// STRAVA is a sports activity tracker
STRAVA SegmentType = "strava"
// Subversion segment
Expand Down Expand Up @@ -275,7 +277,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
DOTNET: func() SegmentWriter { return &segments.Dotnet{} },
EXECUTIONTIME: func() SegmentWriter { return &segments.Executiontime{} },
ELIXIR: func() SegmentWriter { return &segments.Elixir{} },
EXIT: func() SegmentWriter { return &segments.Exit{} },
EXIT: func() SegmentWriter { return &segments.Status{} },
FLUTTER: func() SegmentWriter { return &segments.Flutter{} },
FOSSIL: func() SegmentWriter { return &segments.Fossil{} },
GCP: func() SegmentWriter { return &segments.Gcp{} },
Expand Down Expand Up @@ -314,6 +316,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
SHELL: func() SegmentWriter { return &segments.Shell{} },
SITECORE: func() SegmentWriter { return &segments.Sitecore{} },
SPOTIFY: func() SegmentWriter { return &segments.Spotify{} },
STATUS: func() SegmentWriter { return &segments.Status{} },
STRAVA: func() SegmentWriter { return &segments.Strava{} },
SVN: func() SegmentWriter { return &segments.Svn{} },
SWIFT: func() SegmentWriter { return &segments.Swift{} },
Expand Down
4 changes: 2 additions & 2 deletions src/mock/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,9 @@ func (env *MockedEnvironment) RunShellCommand(shell, command string) string {
return args.String(0)
}

func (env *MockedEnvironment) ErrorCode() int {
func (env *MockedEnvironment) StatusCodes() (int, string) {
args := env.Called()
return args.Int(0)
return args.Int(0), args.String(1)
}

func (env *MockedEnvironment) ExecutionTime() float64 {
Expand Down
9 changes: 5 additions & 4 deletions src/platform/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ var (

type Flags struct {
ErrorCode int
PipeStatus string
Config string
Shell string
ShellVersion string
Expand Down Expand Up @@ -215,7 +216,7 @@ type Environment interface {
GOOS() string
Shell() string
Platform() string
ErrorCode() int
StatusCodes() (int, string)
PathSeparator() string
HasFiles(pattern string) bool
HasFilesInDir(dir, pattern string) bool
Expand Down Expand Up @@ -611,9 +612,9 @@ func (env *Shell) HasCommand(command string) bool {
return false
}

func (env *Shell) ErrorCode() int {
func (env *Shell) StatusCodes() (int, string) {
defer env.Trace(time.Now())
return env.CmdFlags.ErrorCode
return env.CmdFlags.ErrorCode, env.CmdFlags.PipeStatus
}

func (env *Shell) ExecutionTime() float64 {
Expand Down Expand Up @@ -798,7 +799,7 @@ func (env *Shell) TemplateCache() *TemplateCache {
tmplCache.Root = env.Root()
tmplCache.Shell = env.Shell()
tmplCache.ShellVersion = env.CmdFlags.ShellVersion
tmplCache.Code = env.ErrorCode()
tmplCache.Code, _ = env.StatusCodes()
tmplCache.WSL = env.IsWsl()
tmplCache.Segments = make(map[string]interface{})
tmplCache.PromptCount = env.CmdFlags.PromptCount
Expand Down
92 changes: 0 additions & 92 deletions src/segments/exit_test.go

This file was deleted.

109 changes: 109 additions & 0 deletions src/segments/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package segments

import (
"strconv"
"strings"

"github.com/jandedobbeleer/oh-my-posh/src/platform"
"github.com/jandedobbeleer/oh-my-posh/src/properties"
"github.com/jandedobbeleer/oh-my-posh/src/template"
)

const (
StatusTemplate properties.Property = "status_template"
StatusSeparator properties.Property = "status_separator"
)

type Status struct {
props properties.Properties
env platform.Environment

String string
Error bool
Code int

template *template.Text

// Deprecated: Use {{ reason .Code }} instead
Meaning string
}

func (s *Status) Template() string {
return " {{ .String }} "
}

func (s *Status) Enabled() bool {
status, pipeStatus := s.env.StatusCodes()

s.String = s.formatStatus(status, pipeStatus)
// Deprecated: Use {{ reason .Code }} instead
s.Meaning = template.GetReasonFromStatus(status)

if s.props.GetBool(properties.AlwaysEnabled, false) {
return true
}

return s.Error
}

func (s *Status) Init(props properties.Properties, env platform.Environment) {
s.props = props
s.env = env

statusTemplate := s.props.GetString(StatusTemplate, "{{ .Code }}")
s.template = &template.Text{
Template: statusTemplate,
Env: s.env,
}
}

func (s *Status) formatStatus(status int, pipeStatus string) string {
if status != 0 {
s.Error = true
}

if len(pipeStatus) == 0 {
s.Code = status
s.template.Context = s
if text, err := s.template.Render(); err == nil {
return text
}
return strconv.Itoa(status)
}

StatusSeparator := s.props.GetString(StatusSeparator, "|")

var builder strings.Builder

splitted := strings.Split(pipeStatus, " ")
for i, codeStr := range splitted {
write := func(text string) {
if i > 0 {
builder.WriteString(StatusSeparator)
}
builder.WriteString(text)
}

code, err := strconv.Atoi(codeStr)
if err != nil {
write(codeStr)
continue
}

if code != 0 {
s.Error = true
}

s.Code = code
s.template.Context = s
text, err := s.template.Render()
if err != nil {
write(codeStr)
continue
}

write(text)
}

return builder.String()
}
Loading

0 comments on commit f47da95

Please sign in to comment.