refactor(cli): replace cobra and pflag with minimal internal packages

The CLI framework was the largest remaining third-party chunk (545 kB)
and most of its surface went unused - completions are explicitly
disabled, templates and command groups never used. Replace both with
internal packages mirroring the exact API subset in use: a command tree
with persistent flags and nearest-hook semantics, POSIX flag parsing
(--flag=value, --flag value, shorthands and grouping, the -- terminator,
interspersed positionals, unknown-flag allowlisting for the argocd
segment), positional validators, the implicit help command, and the
Windows Explorer double-click guard.

Help, usage and error output were verified byte-identical against the
previous binary across a 47-case golden battery: every command and
subcommand help screen, error formats and exit codes, flag styles
including flags before the subcommand, and init/print rendering.

Shrinks the stripped linux/amd64 binary by 586 kB (13.50 MB -> 12.91 MB;
23.7% below the original 16.93 MB).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qiyvpiy5jR2tyzwZ3zUki5
This commit is contained in:
Claude
2026-07-31 07:40:24 +02:00
committed by Jan De Dobbeleer
parent 98f069cb9d
commit 2a59ed1e50
41 changed files with 1415 additions and 134 deletions
+10 -10
View File
@@ -52,14 +52,14 @@ build/ # CI build helpers
Key paths inside `src/`:
| Path | Purpose |
| ----------------------------- | --------------------------------------------------- |
| `src/segments/` | One `.go` + one `_test.go` per segment |
| `src/config/segment_types.go` | Segment type registry (gob + string constants) |
| `src/cli/` | CLI commands (Cobra); `root.go` is the entry point |
| `src/prompt/engine.go` | Segment rendering loop |
| `src/cache/` | Existing TTL/file/command-path cache infrastructure |
| `src/runtime/` | `Environment` abstraction + mock |
| Path | Purpose |
| ------------------------------ | ----------------------------------------------------- |
| `src/segments/` | One `.go` + one `_test.go` per segment |
| `src/config/segment_types.go` | Segment type registry (gob + string constants) |
| `src/cli/` | CLI commands (cmdtree); `root.go` is the entry point |
| `src/prompt/engine.go` | Segment rendering loop |
| `src/cache/` | Existing TTL/file/command-path cache infrastructure |
| `src/runtime/` | `Environment` abstraction + mock |
## Segment Development
@@ -97,10 +97,10 @@ Supported shells: `bash`, `zsh`, `fish`, `powershell`/`pwsh`, `cmd`, `nu`, `elvi
## CLI Commands
CLI commands use [Cobra](https://github.com/spf13/cobra) and live in `src/cli/`. To add a new
CLI commands use the internal `src/cmdtree` command tree and live in `src/cli/`. To add a new
command:
1. Create `src/cli/<name>.go` with a `var <name>Cmd = &cobra.Command{...}`
1. Create `src/cli/<name>.go` with a `var <name>Cmd = &cmdtree.Command{...}`
2. Register it in `src/cli/root.go` via `RootCmd.AddCommand(<name>Cmd)`
## Caching
+4 -4
View File
@@ -1,17 +1,17 @@
package cli
import (
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
func NoArgsOrOneValidArg(cmd *cobra.Command, args []string) error {
func NoArgsOrOneValidArg(cmd *cmdtree.Command, args []string) error {
if len(args) == 0 {
return nil
}
if err := cobra.ExactArgs(1)(cmd, args); err != nil {
if err := cmdtree.ExactArgs(1)(cmd, args); err != nil {
return err
}
return cobra.OnlyValidArgs(cmd, args)
return cmdtree.OnlyValidArgs(cmd, args)
}
+3 -3
View File
@@ -8,10 +8,10 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
var authCmd = &cobra.Command{
var authCmd = &cmdtree.Command{
Use: "auth [service]",
Short: "Authenticate against a service",
Long: `Authenticate against a service.
@@ -25,7 +25,7 @@ Available services:
"ytmda",
},
Args: NoArgsOrOneValidArg,
Run: func(cmd *cobra.Command, args []string) {
Run: func(cmd *cmdtree.Command, args []string) {
if len(args) == 0 {
_ = cmd.Help()
return
+4 -4
View File
@@ -7,14 +7,14 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
var (
session bool
)
var cacheCmd = &cobra.Command{
var cacheCmd = &cmdtree.Command{
Use: "cache [path|clear|ttl|show]",
Short: "Interact with the oh-my-posh cache",
Long: `Interact with the oh-my-posh cache.
@@ -31,8 +31,8 @@ You can do the following:
cache.TTL,
"show",
},
Args: cobra.RangeArgs(1, 2),
Run: func(cmd *cobra.Command, args []string) {
Args: cmdtree.RangeArgs(1, 2),
Run: func(cmd *cmdtree.Command, args []string) {
if len(args) == 0 {
_ = cmd.Help()
return
+3 -3
View File
@@ -6,10 +6,10 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/segments"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
var claudeCmd = &cobra.Command{
var claudeCmd = &cmdtree.Command{
Use: "claude",
Short: "Render a prompt for Claude Code statusline",
Long: `Render a prompt for Claude Code statusline integration.
@@ -22,7 +22,7 @@ Example usage in Claude Code settings:
"statusLine": {
"command": "oh-my-posh claude --config ~/.config/ohmyposh/claude.toml"
}`,
Args: cobra.NoArgs,
Args: cmdtree.NoArgs,
Run: statuslineRun[segments.ClaudeData](
shell.CLAUDE,
cache.CLAUDECACHE,
+3 -3
View File
@@ -6,12 +6,12 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/cli/dsc"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
"github.com/jandedobbeleer/oh-my-posh/src/config"
basedsc "github.com/jandedobbeleer/oh-my-posh/src/dsc"
"github.com/spf13/cobra"
)
var configCmd = &cobra.Command{
var configCmd = &cmdtree.Command{
Use: "config edit",
Short: "Interact with the config",
Long: `Interact with the config.
@@ -21,7 +21,7 @@ You can export or edit the config (via the editor specified in the environment v
"edit",
},
Args: NoArgsOrOneValidArg,
Run: func(cmd *cobra.Command, args []string) {
Run: func(cmd *cmdtree.Command, args []string) {
if len(args) == 0 {
_ = cmd.Help()
return
+4 -4
View File
@@ -11,7 +11,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
var (
@@ -19,7 +19,7 @@ var (
output string
)
var exportCmd = &cobra.Command{
var exportCmd = &cmdtree.Command{
Use: "export",
Short: "Export your config",
Long: `Export your config.
@@ -35,8 +35,8 @@ Exports the config file "~/myconfig.omp.json" in TOML format and prints the resu
> oh-my-posh config export --output ~/new_config.omp.json
Exports the current config to "~/new_config.omp.json" (in JSON format).`,
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
Args: cmdtree.NoArgs,
Run: func(_ *cmdtree.Command, _ []string) {
if output == "" && format == "" {
// usage error
fmt.Println("neither output path nor export format is specified")
+4 -4
View File
@@ -11,7 +11,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
var (
@@ -20,7 +20,7 @@ var (
themesDir string
)
var dataCmd = &cobra.Command{
var dataCmd = &cmdtree.Command{
Use: "data",
Short: "Export a template data file for your config",
Long: `Export a template data file for your config.
@@ -47,8 +47,8 @@ them into a single sanitized fixture (the most common recorded value per
segment key wins), and writes it to the given output path. --config is
ignored in this mode. This is the single command that regenerates
src/prompt/testdata/fixtures - run from src/.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, _ []string) {
Args: cmdtree.NoArgs,
Run: func(cmd *cmdtree.Command, _ []string) {
cache.Init(os.Getenv("POSH_SHELL"))
if themesDir != "" {
+4 -4
View File
@@ -11,7 +11,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
var (
@@ -25,7 +25,7 @@ var (
svgBackgroundColor string
)
var imageCmd = &cobra.Command{
var imageCmd = &cmdtree.Command{
Use: "image",
Short: "Export your config to an SVG image",
Long: `Export your config to an SVG image.
@@ -70,8 +70,8 @@ Exports the config to an image file called myconfig.svg in the current working d
> oh-my-posh config export image --config ~/myconfig.omp.json --output ~/mytheme.svg
Exports the config to an image file ~/mytheme.svg.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, _ []string) {
Args: cmdtree.NoArgs,
Run: func(cmd *cmdtree.Command, _ []string) {
cache.Init(os.Getenv("POSH_SHELL"))
setConfigFlag()
+3 -3
View File
@@ -6,12 +6,12 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/segments"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
const copilotServiceName = "copilot"
var copilotCmd = &cobra.Command{
var copilotCmd = &cmdtree.Command{
Use: copilotServiceName,
Short: "Render a prompt for GitHub Copilot CLI statusline",
Long: `Render a prompt for GitHub Copilot CLI statusline integration.
@@ -23,7 +23,7 @@ model name, token usage, costs, and more.
Example usage in GitHub Copilot CLI settings (%USERPROFILE%\.copilot\statusline.cmd):
@echo off
oh-my-posh copilot --config %USERPROFILE%\.config\ohmyposh\copilot.toml`,
Args: cobra.NoArgs,
Args: cmdtree.NoArgs,
Run: statuslineRun(
shell.COPILOTCLI,
cache.COPILOTCLICACHE,
+2 -2
View File
@@ -10,7 +10,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -291,7 +291,7 @@ func TestApplyDataFile_DataDeriveIsNoopOnAnUnmarkedFile(t *testing.T) {
}
func TestPrintAndImageCmd_DataDeriveFlagRegistered(t *testing.T) {
for _, cmd := range []*cobra.Command{printCmd, imageCmd} {
for _, cmd := range []*cmdtree.Command{printCmd, imageCmd} {
flag := cmd.Flags().Lookup("data-derive")
require.NotNil(t, flag, "%s must register --data-derive", cmd.Use)
assert.Equal(t, "false", flag.DefValue)
+4 -4
View File
@@ -15,7 +15,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
var (
@@ -27,12 +27,12 @@ func init() {
RootCmd.AddCommand(debugCmd)
}
func createDebugCmd() *cobra.Command {
debugCmd := &cobra.Command{
func createDebugCmd() *cmdtree.Command {
debugCmd := &cmdtree.Command{
Use: "debug",
Short: "Print the prompt in debug mode",
Long: "Print the prompt in debug mode.",
Run: func(_ *cobra.Command, _ []string) {
Run: func(_ *cmdtree.Command, _ []string) {
startTime := time.Now()
log.Enable(plain)
+3 -3
View File
@@ -3,16 +3,16 @@ package cli
import (
"fmt"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
var disableCmd = &cobra.Command{
var disableCmd = &cmdtree.Command{
Use: fmt.Sprintf(toggleUse, "disable"),
Short: "Disable a feature",
Long: fmt.Sprintf(toggleLong, "Disable"),
ValidArgs: toggleArgs,
Args: NoArgsOrOneValidArg,
Run: func(cmd *cobra.Command, args []string) {
Run: func(cmd *cmdtree.Command, args []string) {
if len(args) == 0 {
_ = cmd.Help()
return
+4 -4
View File
@@ -8,7 +8,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
var (
@@ -23,13 +23,13 @@ var (
toggleLong = strings.Join(append([]string{toggleHelpText}, toggleArgs...), "\n- ")
)
var enableCmd = &cobra.Command{
var enableCmd = &cmdtree.Command{
Use: fmt.Sprintf(toggleUse, "enable"),
Short: "Enable a feature",
Long: fmt.Sprintf(toggleLong, "Enable"),
ValidArgs: toggleArgs,
Args: NoArgsOrOneValidArg,
Run: func(cmd *cobra.Command, args []string) {
Run: func(cmd *cmdtree.Command, args []string) {
if len(args) == 0 {
_ = cmd.Help()
return
@@ -42,7 +42,7 @@ func init() {
RootCmd.AddCommand(enableCmd)
}
func toggleFeature(cmd *cobra.Command, feature string, enable bool) {
func toggleFeature(cmd *cmdtree.Command, feature string, enable bool) {
if feature == "" {
_ = cmd.Help()
return
+8 -8
View File
@@ -12,13 +12,13 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
var (
zipFolder string
fontCmd = &cobra.Command{
fontCmd = &cmdtree.Command{
Use: "font",
Short: "Manage fonts",
Long: `Manage fonts.
@@ -29,7 +29,7 @@ List the available Nerd Fonts and install one:
oh-my-posh font install Meslo`,
}
fontListCmd = &cobra.Command{
fontListCmd = &cmdtree.Command{
Use: "list",
Short: "List the available Nerd Fonts",
Long: `List the available Nerd Fonts.
@@ -37,8 +37,8 @@ List the available Nerd Fonts and install one:
Prints one font name per line, so it can be searched or piped:
oh-my-posh font list | grep -i mono`,
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
Args: cmdtree.NoArgs,
Run: func(_ *cmdtree.Command, _ []string) {
fonts, err := font.List()
if err != nil {
log.Error(err)
@@ -53,7 +53,7 @@ Prints one font name per line, so it can be searched or piped:
},
}
fontInstallCmd = &cobra.Command{
fontInstallCmd = &cmdtree.Command{
Use: "install <font>",
Short: "Install a Nerd Font",
Long: `Install a Nerd Font.
@@ -63,8 +63,8 @@ Takes a font name from ` + "`oh-my-posh font list`" + `, a URL, or the path to a
oh-my-posh font install Meslo
oh-my-posh font install https://example.com/font.zip
oh-my-posh font install ./CascadiaCode.zip`,
Args: cobra.ExactArgs(1),
Run: func(_ *cobra.Command, args []string) {
Args: cmdtree.ExactArgs(1),
Run: func(_ *cmdtree.Command, args []string) {
env := &runtime.Terminal{}
env.Init(&runtime.Flags{})
+3 -3
View File
@@ -10,10 +10,10 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
color2 "github.com/gookit/color"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
var getCmd = &cobra.Command{
var getCmd = &cmdtree.Command{
Use: "get [shell|millis|accent|toggles|width]",
Short: "Get a value from oh-my-posh",
Long: `Get a value from oh-my-posh.
@@ -34,7 +34,7 @@ This command is used to get the value of the following variables:
cache.TTL,
},
Args: NoArgsOrOneValidArg,
Run: func(cmd *cobra.Command, args []string) {
Run: func(cmd *cmdtree.Command, args []string) {
if len(args) == 0 {
_ = cmd.Help()
return
+7 -7
View File
@@ -8,14 +8,14 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/cli/dsc"
"github.com/jandedobbeleer/oh-my-posh/src/cmdflag"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
"github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/runtime/path"
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var (
@@ -43,8 +43,8 @@ func init() {
RootCmd.AddCommand(initCmd)
}
func createInitCmd() *cobra.Command {
initCmd := &cobra.Command{
func createInitCmd() *cmdtree.Command {
initCmd := &cmdtree.Command{
Use: "init [bash|zsh|fish|powershell|pwsh|cmd|nu|elvish|xonsh|yash]",
Short: "Initialize your shell and config",
Long: `Initialize your shell and config.
@@ -52,7 +52,7 @@ func createInitCmd() *cobra.Command {
See the documentation to initialize your shell: https://ohmyposh.dev/docs/installation/prompt.`,
ValidArgs: supportedShells,
Args: NoArgsOrOneValidArg,
Run: func(cmd *cobra.Command, args []string) {
Run: func(cmd *cmdtree.Command, args []string) {
if len(args) == 0 {
_ = cmd.Help()
return
@@ -143,7 +143,7 @@ func runInit(sh, command string) {
fmt.Print(output)
}
func getFullCommand(cmd *cobra.Command, args []string) string {
func getFullCommand(cmd *cmdtree.Command, args []string) string {
// Start with the command path
cmdPath := cmd.CommandPath()
@@ -153,7 +153,7 @@ func getFullCommand(cmd *cobra.Command, args []string) string {
}
// Add flags that were actually set
cmd.Flags().VisitAll(func(flag *pflag.Flag) {
cmd.Flags().VisitAll(func(flag *cmdflag.Flag) {
if !flag.Changed {
return
}
+4 -4
View File
@@ -6,17 +6,17 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/cli/upgrade"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
"github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/spf13/cobra"
)
var noticeCmd = &cobra.Command{
var noticeCmd = &cmdtree.Command{
Use: "notice",
Short: "Print the upgrade notice when a new version is available.",
Long: "Print the upgrade notice when a new version is available.",
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
Args: cmdtree.NoArgs,
Run: func(_ *cmdtree.Command, _ []string) {
env := &runtime.Terminal{}
env.Init(&runtime.Flags{})
+4 -4
View File
@@ -9,7 +9,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
var (
@@ -40,8 +40,8 @@ func init() {
RootCmd.AddCommand(printCmd)
}
func createPrintCmd() *cobra.Command {
printCmd := &cobra.Command{
func createPrintCmd() *cmdtree.Command {
printCmd := &cmdtree.Command{
Use: "print [debug|primary|secondary|transient|transient-right|right|tooltip|valid|error|preview]",
Short: "Print the prompt/context",
Long: "Print one of the prompts based on the location/use-case.",
@@ -58,7 +58,7 @@ func createPrintCmd() *cobra.Command {
prompt.PREVIEW,
},
Args: NoArgsOrOneValidArg,
Run: func(cmd *cobra.Command, args []string) {
Run: func(cmd *cmdtree.Command, args []string) {
if len(args) == 0 {
_ = cmd.Help()
return
+6 -6
View File
@@ -9,8 +9,8 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/build"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
"github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/spf13/cobra"
)
var (
@@ -27,14 +27,14 @@ var (
initialize bool
)
var RootCmd = &cobra.Command{
var RootCmd = &cmdtree.Command{
Use: "oh-my-posh",
Short: "oh-my-posh is a tool to render your prompt",
Long: `oh-my-posh is a cross platform tool to render your prompt.
It can use the same configuration everywhere to offer a consistent
experience, regardless of where you are. For a detailed guide
on getting started, have a look at the docs at https://ohmyposh.dev`,
Run: func(cmd *cobra.Command, args []string) {
Run: func(cmd *cmdtree.Command, args []string) {
if initialize {
runInit(strings.ToLower(shellName), getFullCommand(cmd, args))
return
@@ -47,7 +47,7 @@ on getting started, have a look at the docs at https://ohmyposh.dev`,
_ = cmd.Help()
},
PersistentPreRun: func(cmd *cobra.Command, args []string) {
PersistentPreRun: func(cmd *cmdtree.Command, args []string) {
configEnv := os.Getenv("POSH_CONFIG")
if configEnv != "" && configFlag == "" {
configFlag = configEnv
@@ -65,7 +65,7 @@ on getting started, have a look at the docs at https://ohmyposh.dev`,
log.Debug("version:", build.Version)
log.Debug("command:", getFullCommand(cmd, args))
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
PersistentPostRun: func(cmd *cmdtree.Command, args []string) {
defer func() {
if exitcode != 0 {
os.Exit(exitcode)
@@ -104,7 +104,7 @@ func Execute() {
// of milliseconds per prompt. Explorer never passes arguments, so the
// check is only needed when there are none.
if len(os.Args) > 1 {
cobra.MousetrapHelpText = ""
cmdtree.ExplorerLaunchHelpText = ""
}
if err := RootCmd.Execute(); err != nil {
+5 -5
View File
@@ -13,7 +13,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
// Unix only - used by shells that cannot hold a child's stdin open across
@@ -66,13 +66,13 @@ const (
// stdout: "<id>\x1f<payload>\x00". \x1f is the ASCII unit separator.
const serveIDMarker = "\x1f"
func createServeCmd() *cobra.Command {
serveCmd := &cobra.Command{
func createServeCmd() *cmdtree.Command {
serveCmd := &cmdtree.Command{
Use: "serve",
Short: "Start a persistent prompt server that streams prompt updates over stdio",
Hidden: true,
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
Args: cmdtree.NoArgs,
Run: func(_ *cmdtree.Command, _ []string) {
if shellName == "" {
shellName = shell.GENERIC
}
+3 -3
View File
@@ -5,12 +5,12 @@ import (
"os"
"github.com/jandedobbeleer/oh-my-posh/src/cli/dsc"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
basedsc "github.com/jandedobbeleer/oh-my-posh/src/dsc"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/spf13/cobra"
)
var shellCmd = &cobra.Command{
var shellCmd = &cmdtree.Command{
Use: "shell get",
Short: "Get the shell name",
Long: `Get the shell name.
@@ -21,7 +21,7 @@ This command retrieves the name of the current shell being used.`,
"get",
},
Args: NoArgsOrOneValidArg,
Run: func(cmd *cobra.Command, args []string) {
Run: func(cmd *cmdtree.Command, args []string) {
if len(args) == 0 {
_ = cmd.Help()
return
+3 -3
View File
@@ -14,7 +14,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
// statuslineRun returns a cobra Run function for statusline commands.
@@ -23,8 +23,8 @@ import (
// cacheKey is the session cache key under which data is stored.
// sessionID extracts the session ID from parsed data so it can be set as POSH_SESSION_ID.
// defaultCfg is called when no --config flag is provided or parsing fails.
func statuslineRun[T any](shellConst, cacheKey string, sessionID func(*T) string, defaultCfg func() *config.Config) func(*cobra.Command, []string) {
return func(cmd *cobra.Command, _ []string) {
func statuslineRun[T any](shellConst, cacheKey string, sessionID func(*T) string, defaultCfg func() *config.Config) func(*cmdtree.Command, []string) {
return func(cmd *cmdtree.Command, _ []string) {
log.Debugf("%s command started", shellConst)
stdinData, err := io.ReadAll(os.Stdin)
+5 -5
View File
@@ -9,7 +9,7 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/shell"
"github.com/jandedobbeleer/oh-my-posh/src/template"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
var streamCmd = createStreamCmd()
@@ -18,8 +18,8 @@ func init() {
RootCmd.AddCommand(streamCmd)
}
func createStreamCmd() *cobra.Command {
streamCmd := &cobra.Command{
func createStreamCmd() *cmdtree.Command {
streamCmd := &cmdtree.Command{
Use: "stream",
Short: "Stream the prompt with incremental updates",
Long: `Stream the primary prompt with incremental updates as segments complete.
@@ -29,8 +29,8 @@ Records prefixed with a record separator byte (\x1e) carry the transient prompt,
which shells cache so no additional CLI call is needed on line acceptance.
The shell can read records incrementally and update the display.
Command exits when all segments are resolved.`,
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
Args: cmdtree.NoArgs,
Run: func(_ *cmdtree.Command, _ []string) {
if shellName == "" {
shellName = shell.GENERIC
}
+4 -4
View File
@@ -5,16 +5,16 @@ import (
"strings"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/spf13/cobra"
)
var toggleCmd = &cobra.Command{
var toggleCmd = &cmdtree.Command{
Use: "toggle segment1 segment2 ...",
Short: "Toggle one or more segments on/off",
Long: "Toggle one or more segments on/off on the fly. Multiple segments can be specified separated by spaces.",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
Args: cmdtree.MinimumNArgs(1),
Run: func(cmd *cmdtree.Command, args []string) {
if len(args) == 0 {
_ = cmd.Help()
return
+4 -4
View File
@@ -11,12 +11,12 @@ import (
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/cli/upgrade"
"github.com/jandedobbeleer/oh-my-posh/src/cli/upgrade/tui"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
"github.com/jandedobbeleer/oh-my-posh/src/config"
"github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/jandedobbeleer/oh-my-posh/src/terminal"
"github.com/jandedobbeleer/oh-my-posh/src/text"
"github.com/spf13/cobra"
)
var (
@@ -24,12 +24,12 @@ var (
auto bool
)
var upgradeCmd = &cobra.Command{
var upgradeCmd = &cmdtree.Command{
Use: "upgrade",
Short: "Upgrade when a new version is available.",
Long: "Upgrade when a new version is available.",
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
Args: cmdtree.NoArgs,
Run: func(_ *cmdtree.Command, _ []string) {
var startTime time.Time
if debug {
+4 -4
View File
@@ -4,19 +4,19 @@ import (
"fmt"
"github.com/jandedobbeleer/oh-my-posh/src/build"
"github.com/spf13/cobra"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
)
var (
verbose bool
)
var versionCmd = &cobra.Command{
var versionCmd = &cmdtree.Command{
Use: "version",
Short: "Print the version",
Long: "Print the version number of oh-my-posh.",
Args: cobra.NoArgs,
Run: func(_ *cobra.Command, _ []string) {
Args: cmdtree.NoArgs,
Run: func(_ *cmdtree.Command, _ []string) {
if !verbose {
fmt.Println(build.Version)
return
+448
View File
@@ -0,0 +1,448 @@
// Package cmdflag is a minimal POSIX-style command-line flag parser covering
// the API surface oh-my-posh uses: typed Var registration, POSIX-style
// parsing (--flag=value, --flag value, shorthands, the -- terminator,
// interspersed positionals), hidden flags, and usage rendering matching
// common CLI flag conventions byte for byte for the flag types in use.
package cmdflag
import (
"fmt"
"strconv"
"strings"
)
type ErrorHandling int
const (
ContinueOnError ErrorHandling = iota
ExitOnError
)
const (
boolType = "bool"
trueStr = "true"
)
// Value is the interface to the dynamic value stored in a flag.
type Value interface {
String() string
Set(string) error
Type() string
}
type Flag struct {
Name string
Shorthand string
Usage string
Value Value
DefValue string
Hidden bool
Changed bool
}
type FlagSet struct {
name string
flags map[string]*Flag
shorthands map[byte]*Flag
order []*Flag
args []string
ParseErrorsAllowlist struct {
UnknownFlags bool
}
}
func NewFlagSet(name string, _ ErrorHandling) *FlagSet {
return &FlagSet{
name: name,
flags: make(map[string]*Flag),
shorthands: make(map[byte]*Flag),
}
}
// value implementations
type stringValue struct{ p *string }
func (v stringValue) String() string { return *v.p }
func (v stringValue) Set(s string) error { *v.p = s; return nil }
func (v stringValue) Type() string { return "string" }
type boolValue struct{ p *bool }
func (v boolValue) String() string { return strconv.FormatBool(*v.p) }
func (v boolValue) Set(s string) error {
b, err := strconv.ParseBool(s)
*v.p = b
return err
}
func (v boolValue) Type() string { return boolType }
type intValue struct{ p *int }
func (v intValue) String() string { return strconv.Itoa(*v.p) }
func (v intValue) Set(s string) error {
i, err := strconv.ParseInt(s, 0, 64)
*v.p = int(i)
return err
}
func (v intValue) Type() string { return "int" }
type float64Value struct{ p *float64 }
func (v float64Value) String() string { return strconv.FormatFloat(*v.p, 'g', -1, 64) }
func (v float64Value) Set(s string) error {
f, err := strconv.ParseFloat(s, 64)
*v.p = f
return err
}
func (v float64Value) Type() string { return "float64" }
// registration
func (f *FlagSet) Var(value Value, name, shorthand, usage string) *Flag {
flag := &Flag{
Name: name,
Shorthand: shorthand,
Usage: usage,
Value: value,
DefValue: value.String(),
}
f.flags[name] = flag
f.order = append(f.order, flag)
if shorthand != "" {
f.shorthands[shorthand[0]] = flag
}
return flag
}
func (f *FlagSet) StringVar(p *string, name, value, usage string) {
f.StringVarP(p, name, "", value, usage)
}
func (f *FlagSet) StringVarP(p *string, name, shorthand, value, usage string) {
*p = value
f.Var(stringValue{p}, name, shorthand, usage)
}
func (f *FlagSet) String(name, value, usage string) *string {
p := new(string)
f.StringVar(p, name, value, usage)
return p
}
func (f *FlagSet) BoolVar(p *bool, name string, value bool, usage string) {
f.BoolVarP(p, name, "", value, usage)
}
func (f *FlagSet) BoolVarP(p *bool, name, shorthand string, value bool, usage string) {
*p = value
f.Var(boolValue{p}, name, shorthand, usage)
}
func (f *FlagSet) IntVar(p *int, name string, value int, usage string) {
f.IntVarP(p, name, "", value, usage)
}
func (f *FlagSet) IntVarP(p *int, name, shorthand string, value int, usage string) {
*p = value
f.Var(intValue{p}, name, shorthand, usage)
}
func (f *FlagSet) Float64Var(p *float64, name string, value float64, usage string) {
*p = value
f.Var(float64Value{p}, name, "", usage)
}
// inspection
func (f *FlagSet) Lookup(name string) *Flag {
return f.flags[name]
}
func (f *FlagSet) ShorthandLookup(name string) *Flag {
if name == "" {
return nil
}
return f.shorthands[name[0]]
}
func (f *FlagSet) Changed(name string) bool {
flag := f.flags[name]
return flag != nil && flag.Changed
}
func (f *FlagSet) MarkHidden(name string) error {
flag := f.flags[name]
if flag == nil {
return fmt.Errorf("flag %q does not exist", name)
}
flag.Hidden = true
return nil
}
// VisitAll visits the flags in registration order rather than
// lexicographically: the sole caller formats a command line and is
// order-insensitive, and usage rendering sorts separately.
func (f *FlagSet) VisitAll(fn func(*Flag)) {
for _, flag := range f.order {
fn(flag)
}
}
// AddFlagSet adds flags from another set that are not yet present.
func (f *FlagSet) AddFlagSet(other *FlagSet) {
if other == nil {
return
}
other.VisitAll(func(flag *Flag) {
if f.flags[flag.Name] == nil {
f.flags[flag.Name] = flag
f.order = append(f.order, flag)
if flag.Shorthand != "" && f.shorthands[flag.Shorthand[0]] == nil {
f.shorthands[flag.Shorthand[0]] = flag
}
}
})
}
func (f *FlagSet) HasFlags() bool {
return len(f.order) > 0
}
func (f *FlagSet) HasAvailableFlags() bool {
for _, flag := range f.order {
if !flag.Hidden {
return true
}
}
return false
}
func (f *FlagSet) Args() []string {
return f.args
}
// parsing
func (f *FlagSet) Parse(arguments []string) error {
f.args = make([]string, 0, len(arguments))
for len(arguments) > 0 {
arg := arguments[0]
arguments = arguments[1:]
switch {
case arg == "--":
f.args = append(f.args, arguments...)
return nil
case strings.HasPrefix(arg, "--"):
var err error
arguments, err = f.parseLong(arg[2:], arguments)
if err != nil {
return err
}
case strings.HasPrefix(arg, "-") && len(arg) > 1:
var err error
arguments, err = f.parseShort(arg[1:], arguments)
if err != nil {
return err
}
default:
f.args = append(f.args, arg)
}
}
return nil
}
func (f *FlagSet) parseLong(name string, rest []string) ([]string, error) {
value := ""
hasValue := false
if i := strings.Index(name, "="); i >= 0 {
value = name[i+1:]
name = name[:i]
hasValue = true
}
flag := f.flags[name]
if flag == nil {
if !f.ParseErrorsAllowlist.UnknownFlags {
return rest, fmt.Errorf("unknown flag: --%s", name)
}
// an unknown flag given as "--flag value" swallows the
// value token unless the next token is itself a flag
if !hasValue && len(rest) > 0 && !strings.HasPrefix(rest[0], "-") {
return rest[1:], nil
}
return rest, nil
}
switch {
case hasValue:
case flag.Value.Type() == boolType:
value = trueStr
case len(rest) > 0:
value = rest[0]
rest = rest[1:]
default:
return rest, fmt.Errorf("flag needs an argument: --%s", name)
}
if err := flag.Value.Set(value); err != nil {
return rest, fmt.Errorf("invalid argument %q for \"--%s\" flag: %v", value, flag.Name, err)
}
flag.Changed = true
return rest, nil
}
func (f *FlagSet) parseShort(shorthands string, rest []string) ([]string, error) {
for len(shorthands) > 0 {
c := shorthands[0]
flag := f.shorthands[c]
if flag == nil {
if f.ParseErrorsAllowlist.UnknownFlags {
// drop the remainder of the group and a
// separate value token unless it is itself a flag
if len(shorthands) == 1 && len(rest) > 0 && !strings.HasPrefix(rest[0], "-") {
return rest[1:], nil
}
return rest, nil
}
return rest, fmt.Errorf("unknown shorthand flag: %q in -%s", c, shorthands)
}
value := ""
switch {
case len(shorthands) > 2 && shorthands[1] == '=':
value = shorthands[2:]
shorthands = ""
case flag.Value.Type() == boolType:
value = trueStr
shorthands = shorthands[1:]
case len(shorthands) > 1:
value = shorthands[1:]
shorthands = ""
case len(rest) > 0:
value = rest[0]
rest = rest[1:]
shorthands = ""
default:
return rest, fmt.Errorf("flag needs an argument: %q in -%s", c, shorthands)
}
if err := flag.Value.Set(value); err != nil {
return rest, fmt.Errorf("invalid argument %q for \"-%s, --%s\" flag: %v", value, flag.Shorthand, flag.Name, err)
}
flag.Changed = true
}
return rest, nil
}
// usage rendering
// FlagUsages renders the non-hidden flags sorted by name, shorthand column,
// type name after the flag, defaults in parentheses when they differ from
// the zero value, usage aligned in a single column.
func (f *FlagSet) FlagUsages() string {
lines := make([]string, 0, len(f.order))
maxlen := 0
flags := make([]*Flag, len(f.order))
copy(flags, f.order)
sortFlags(flags)
for _, flag := range flags {
if flag.Hidden {
continue
}
var line string
if flag.Shorthand != "" {
line = fmt.Sprintf(" -%s, --%s", flag.Shorthand, flag.Name)
}
if flag.Shorthand == "" {
line = fmt.Sprintf(" --%s", flag.Name)
}
if name := typeName(flag); name != "" {
line += " " + name
}
line += "\x00"
if len(line) > maxlen {
maxlen = len(line)
}
line += flag.Usage
if defValue, ok := defaultValue(flag); ok {
line += fmt.Sprintf(" (default %s)", defValue)
}
lines = append(lines, line)
}
var sb strings.Builder
for _, line := range lines {
sidx := strings.Index(line, "\x00")
sb.WriteString(line[:sidx])
// the gap is maxlen-sidx+2 spaces wide to match the implicit
// separators of a Fprintln(left, spacing, usage)-style layout
sb.WriteString(strings.Repeat(" ", maxlen-sidx+2))
sb.WriteString(line[sidx+1:])
sb.WriteString("\n")
}
return sb.String()
}
func sortFlags(flags []*Flag) {
for i := 1; i < len(flags); i++ {
for j := i; j > 0 && flags[j].Name < flags[j-1].Name; j-- {
flags[j], flags[j-1] = flags[j-1], flags[j]
}
}
}
func typeName(flag *Flag) string {
switch flag.Value.Type() {
case boolType:
return ""
case "float64":
return "float"
case "int64":
return "int"
default:
return flag.Value.Type()
}
}
func defaultValue(flag *Flag) (string, bool) {
switch flag.Value.Type() {
case boolType:
return flag.DefValue, flag.DefValue == trueStr
case "string":
return fmt.Sprintf("%q", flag.DefValue), flag.DefValue != ""
default:
return flag.DefValue, flag.DefValue != "0"
}
}
+107
View File
@@ -0,0 +1,107 @@
package cmdflag
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseStyles(t *testing.T) {
cases := []struct {
Case string
Expected string
Args []string
Rest []string
Number int
Bool bool
}{
{Case: "long with space", Args: []string{"--name", "x"}, Expected: "x", Rest: []string{}},
{Case: "long with equals", Args: []string{"--name=x"}, Expected: "x", Rest: []string{}},
{Case: "shorthand with space", Args: []string{"-n", "x"}, Expected: "x", Rest: []string{}},
{Case: "shorthand joined", Args: []string{"-nx"}, Expected: "x", Rest: []string{}},
{Case: "shorthand with equals", Args: []string{"-n=x"}, Expected: "x", Rest: []string{}},
{Case: "bool does not eat value", Args: []string{"--flag", "positional"}, Bool: true, Rest: []string{"positional"}},
{Case: "terminator", Args: []string{"--", "--name", "x"}, Rest: []string{"--name", "x"}},
{Case: "interspersed", Args: []string{"a", "--name", "x", "b"}, Expected: "x", Rest: []string{"a", "b"}},
{Case: "int value", Args: []string{"--count", "42"}, Number: 42, Rest: []string{}},
{Case: "combined bool shorthands", Args: []string{"-fv"}, Bool: true, Rest: []string{}},
}
for _, tc := range cases {
var name string
var flag, verbose bool
var count int
fs := NewFlagSet("test", ContinueOnError)
fs.StringVarP(&name, "name", "n", "", "")
fs.BoolVarP(&flag, "flag", "f", false, "")
fs.BoolVarP(&verbose, "verbose", "v", false, "")
fs.IntVar(&count, "count", 0, "")
err := fs.Parse(tc.Args)
assert.NoError(t, err, tc.Case)
assert.Equal(t, tc.Expected, name, tc.Case)
assert.Equal(t, tc.Bool, flag, tc.Case)
assert.Equal(t, tc.Number, count, tc.Case)
assert.Equal(t, tc.Rest, fs.Args(), tc.Case)
}
}
func TestParseErrors(t *testing.T) {
fs := NewFlagSet("test", ContinueOnError)
var name string
fs.StringVar(&name, "name", "", "")
err := fs.Parse([]string{"--unknown"})
assert.EqualError(t, err, "unknown flag: --unknown")
err = fs.Parse([]string{"-x"})
assert.EqualError(t, err, `unknown shorthand flag: 'x' in -x`)
err = fs.Parse([]string{"--name"})
assert.EqualError(t, err, "flag needs an argument: --name")
}
func TestUnknownFlagsAllowlist(t *testing.T) {
// the argocd segment parses ARGOCD_OPTS this way
fs := NewFlagSet("test", ContinueOnError)
fs.ParseErrorsAllowlist.UnknownFlags = true
fs.String("config", "", "")
err := fs.Parse([]string{"--grpc-web", "--server", "foo.com", "--config", "x", "--insecure"})
assert.NoError(t, err)
assert.Equal(t, "x", fs.Lookup("config").Value.String())
}
func TestChangedAndDefaults(t *testing.T) {
fs := NewFlagSet("test", ContinueOnError)
var escape bool
fs.BoolVar(&escape, "escape", true, "escape the output")
assert.False(t, fs.Changed("escape"))
assert.NoError(t, fs.Parse([]string{"--escape=false"}))
assert.True(t, fs.Changed("escape"))
assert.False(t, escape)
assert.Equal(t, "true", fs.Lookup("escape").DefValue)
}
func TestFlagUsages(t *testing.T) {
fs := NewFlagSet("test", ContinueOnError)
var name, pswd string
var force, escape bool
var width int
fs.StringVarP(&name, "config", "c", "", "config file path")
fs.BoolVarP(&force, "force", "f", false, "force rendering the segments")
fs.BoolVar(&escape, "escape", true, "escape the ANSI sequences for the shell")
fs.IntVarP(&width, "terminal-width", "w", 0, "width of the terminal")
fs.StringVar(&pswd, "pswd", "", "hidden flag")
_ = fs.MarkHidden("pswd")
expected := ` -c, --config string config file path
--escape escape the ANSI sequences for the shell (default true)
-f, --force force rendering the segments
-w, --terminal-width int width of the terminal
`
assert.Equal(t, expected, fs.FlagUsages())
}
+538
View File
@@ -0,0 +1,538 @@
// Package cmdtree is a minimal command-line command tree implementation covering
// the API surface oh-my-posh uses: a command tree with persistent flags and
// hooks, POSIX flag parsing via the sibling cmdflag package, positional
// argument validators, and help/usage output matching common CLI framework
// conventions byte for byte for the features in use.
package cmdtree
import (
"fmt"
"io"
"os"
"slices"
"sort"
"strings"
"github.com/jandedobbeleer/oh-my-posh/src/cmdflag"
)
// ExplorerLaunchHelpText is shown on Windows when the binary is launched from
// Explorer instead of a terminal. Setting it to "" disables the check.
var ExplorerLaunchHelpText = `This is a command line tool.
You need to open cmd.exe and run it from there.
`
type PositionalArgs func(cmd *Command, args []string) error
type Command struct {
out io.Writer
flags *cmdflag.FlagSet
parent *Command
PersistentPostRun func(cmd *Command, args []string)
pflags *cmdflag.FlagSet
PersistentPreRun func(cmd *Command, args []string)
Args PositionalArgs
Run func(cmd *Command, args []string)
Example string
Long string
Short string
Use string
ValidArgs []string
commands []*Command
requiredFlags []string
Aliases []string
setArgs []string
Hidden bool
helpRequested bool
CompletionOptions struct{ DisableDefaultCmd bool }
}
// SetOut redirects help output to a caller-supplied writer instead of stdout.
func (c *Command) SetOut(w io.Writer) {
c.out = w
}
func (c *Command) outWriter() io.Writer {
for cmd := c; cmd != nil; cmd = cmd.parent {
if cmd.out != nil {
return cmd.out
}
}
return os.Stdout
}
func (c *Command) Name() string {
name, _, _ := strings.Cut(c.Use, " ")
return name
}
func (c *Command) CommandPath() string {
if c.parent == nil {
return c.Name()
}
return c.parent.CommandPath() + " " + c.Name()
}
func (c *Command) UseLine() string {
useLine := c.Use
if c.parent != nil {
useLine = c.parent.CommandPath() + " " + c.Use
}
if c.Flags().HasAvailableFlags() && !strings.Contains(useLine, "[flags]") {
useLine += " [flags]"
}
return useLine
}
func (c *Command) Root() *Command {
if c.parent == nil {
return c
}
return c.parent.Root()
}
func (c *Command) AddCommand(cmds ...*Command) {
for _, cmd := range cmds {
cmd.parent = c
c.commands = append(c.commands, cmd)
}
}
func (c *Command) Flags() *cmdflag.FlagSet {
if c.flags == nil {
c.flags = cmdflag.NewFlagSet(c.Name(), cmdflag.ContinueOnError)
}
return c.flags
}
func (c *Command) PersistentFlags() *cmdflag.FlagSet {
if c.pflags == nil {
c.pflags = cmdflag.NewFlagSet(c.Name(), cmdflag.ContinueOnError)
}
return c.pflags
}
func (c *Command) SetArgs(args []string) {
c.setArgs = args
}
// MarkPersistentFlagRequired only applies to a flag registered on this
// command's own persistent set and errors otherwise.
func (c *Command) MarkPersistentFlagRequired(name string) error {
flag := c.PersistentFlags().Lookup(name)
if flag == nil {
return fmt.Errorf("no such flag -%v", name)
}
c.requiredFlags = append(c.requiredFlags, name)
return nil
}
func (c *Command) hasSubCommands() bool {
for _, cmd := range c.commands {
if !cmd.Hidden && cmd.Name() != "help" {
return true
}
}
return false
}
func (c *Command) findChild(name string) *Command {
for _, cmd := range c.commands {
if cmd.Name() == name {
return cmd
}
if slices.Contains(cmd.Aliases, name) {
return cmd
}
}
return nil
}
// mergedFlags returns the command's local flags plus every ancestor's
// persistent flags, with the auto help flag registered.
func (c *Command) mergedFlags() *cmdflag.FlagSet {
merged := cmdflag.NewFlagSet(c.Name(), cmdflag.ContinueOnError)
merged.AddFlagSet(c.Flags())
for cmd := c; cmd != nil; cmd = cmd.parent {
merged.AddFlagSet(cmd.pflags)
}
return merged
}
// inheritedFlags returns the ancestors' persistent flags, rendered in help
// output as the "Global Flags" section.
func (c *Command) inheritedFlags() *cmdflag.FlagSet {
inherited := cmdflag.NewFlagSet(c.Name(), cmdflag.ContinueOnError)
for cmd := c.parent; cmd != nil; cmd = cmd.parent {
inherited.AddFlagSet(cmd.pflags)
}
return inherited
}
// localFlags returns the command's own flags: declared local flags, its own
// persistent flags, and - only when already initialized - the help flag,
// so the help flag is only listed once a caller has actually requested help.
// The unknown-help-topic path renders root usage without a help flag.
func (c *Command) localFlags() *cmdflag.FlagSet {
local := cmdflag.NewFlagSet(c.Name(), cmdflag.ContinueOnError)
local.AddFlagSet(c.Flags())
local.AddFlagSet(c.pflags)
return local
}
func (c *Command) registerHelpFlag() {
if c.Flags().Lookup("help") != nil {
return
}
c.Flags().BoolVarP(&c.helpRequested, "help", "h", false, "help for "+c.Name())
}
// Execute parses os.Args (or SetArgs), routes to the addressed subcommand,
// validates positionals, and runs the hooks and the command.
func (c *Command) Execute() error {
if c.parent == nil {
checkExplorerLaunch()
c.ensureHelpCommand()
}
args := c.setArgs
if args == nil {
args = os.Args[1:]
}
cmd, remaining, err := c.route(args)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
fmt.Fprintf(os.Stderr, "Run '%s --help' for usage.\n", c.CommandPath())
return err
}
return cmd.execute(remaining)
}
// route walks the command tree: at each level the first token that is not a
// flag (accounting for flags that consume a value) addresses a child.
func (c *Command) route(args []string) (*Command, []string, error) {
current := c
for {
name, ok := firstPositional(current, args)
if !ok {
return current, args, nil
}
child := current.findChild(name)
if child == nil {
if current == c && len(current.commands) > 0 {
return nil, nil, fmt.Errorf("unknown command %q for %q", name, c.CommandPath())
}
return current, args, nil
}
args = removeFirst(args, name)
current = child
}
}
// firstPositional finds the first argument that cannot be a flag or a flag
// value at this command level.
func firstPositional(c *Command, args []string) (string, bool) {
flags := c.mergedFlags()
for i := 0; i < len(args); i++ {
arg := args[i]
switch {
case arg == "--":
return "", false
case strings.HasPrefix(arg, "--"):
name, _, hasValue := strings.Cut(arg[2:], "=")
if hasValue {
continue
}
if flag := flags.Lookup(name); flag != nil && flag.Value.Type() != "bool" {
i++ // skip the flag's value
}
case strings.HasPrefix(arg, "-") && len(arg) > 1:
if strings.Contains(arg, "=") {
continue
}
shorthand := arg[len(arg)-1:]
if flag := flags.ShorthandLookup(shorthand); flag != nil && flag.Value.Type() != "bool" {
i++ // skip the flag's value
}
default:
return arg, true
}
}
return "", false
}
func removeFirst(args []string, value string) []string {
result := make([]string, 0, len(args)-1)
removed := false
for _, arg := range args {
if !removed && arg == value {
removed = true
continue
}
result = append(result, arg)
}
return result
}
func (c *Command) execute(args []string) error {
c.registerHelpFlag()
flags := c.mergedFlags()
if err := flags.Parse(args); err != nil {
return c.flagError(err)
}
if c.helpRequested {
return c.Help()
}
for _, name := range c.requiredFlags {
if !flags.Changed(name) {
return c.flagError(fmt.Errorf("required flag(s) \"%s\" not set", name))
}
}
positionals := flags.Args()
if c.Args != nil {
if err := c.Args(c, positionals); err != nil {
return c.flagError(err)
}
}
if c.Run == nil {
return c.Help()
}
if hook := c.findHook(func(cmd *Command) func(*Command, []string) { return cmd.PersistentPreRun }); hook != nil {
hook(c, positionals)
}
c.Run(c, positionals)
if hook := c.findHook(func(cmd *Command) func(*Command, []string) { return cmd.PersistentPostRun }); hook != nil {
hook(c, positionals)
}
return nil
}
// findHook returns the nearest defined hook walking up the tree, so only the
// closest one runs.
func (c *Command) findHook(get func(*Command) func(*Command, []string)) func(*Command, []string) {
for cmd := c; cmd != nil; cmd = cmd.parent {
if hook := get(cmd); hook != nil {
return hook
}
}
return nil
}
func (c *Command) flagError(err error) error {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
// print the usage string via Println, leaving a trailing blank line
fmt.Fprintln(os.Stderr, c.usageString())
return err
}
// ensureHelpCommand adds the implicit help command if none is registered.
func (c *Command) ensureHelpCommand() {
if c.findChild("help") != nil {
return
}
c.AddCommand(&Command{
Use: "help [command]",
Short: "Help about any command",
Long: `Help provides help for any command in the application.
Simply type ` + c.Name() + ` help [path to command] for full details.`,
Run: func(_ *Command, args []string) {
target := c
for _, name := range args {
child := target.findChild(name)
if child == nil {
fmt.Printf("Unknown help topic %#q\n", args)
fmt.Fprint(os.Stderr, c.usageString())
return
}
target = child
}
_ = target.Help()
},
})
}
// help output
func (c *Command) Help() error {
c.registerHelpFlag()
long := c.Long
if long == "" {
long = c.Short
}
if long != "" {
fmt.Fprintln(c.outWriter(), strings.TrimRight(long, "\n"))
fmt.Fprintln(c.outWriter())
}
fmt.Fprint(c.outWriter(), c.usageString())
return nil
}
func (c *Command) usageString() string {
var sb strings.Builder
sb.WriteString("Usage:\n")
if c.Run != nil || !c.hasSubCommands() {
sb.WriteString(" " + c.UseLine() + "\n")
}
if c.hasSubCommands() {
sb.WriteString(" " + c.CommandPath() + " [command]\n")
}
if len(c.Aliases) > 0 {
sb.WriteString("\nAliases:\n")
sb.WriteString(" " + c.Name() + ", " + strings.Join(c.Aliases, ", ") + "\n")
}
if c.Example != "" {
sb.WriteString("\nExamples:\n")
sb.WriteString(c.Example + "\n")
}
if c.hasSubCommands() {
sb.WriteString("\nAvailable Commands:\n")
commands := make([]*Command, 0, len(c.commands))
for _, cmd := range c.commands {
if !cmd.Hidden {
commands = append(commands, cmd)
}
}
sort.Slice(commands, func(i, j int) bool { return commands[i].Name() < commands[j].Name() })
padding := 11
for _, cmd := range commands {
if len(cmd.Name())+2 > padding {
padding = len(cmd.Name()) + 2
}
}
for _, cmd := range commands {
fmt.Fprintf(&sb, " %-*s %s\n", padding, cmd.Name(), cmd.Short)
}
}
if local := c.localFlags(); local.HasAvailableFlags() {
sb.WriteString("\nFlags:\n")
sb.WriteString(local.FlagUsages())
}
if inherited := c.inheritedFlags(); inherited.HasAvailableFlags() {
sb.WriteString("\nGlobal Flags:\n")
sb.WriteString(inherited.FlagUsages())
}
if c.hasSubCommands() {
sb.WriteString("\nUse \"" + c.CommandPath() + " [command] --help\" for more information about a command.\n")
}
return sb.String()
}
// positional argument validators
func NoArgs(cmd *Command, args []string) error {
if len(args) > 0 {
return fmt.Errorf("unknown command %q for %q", args[0], cmd.CommandPath())
}
return nil
}
func ExactArgs(n int) PositionalArgs {
return func(_ *Command, args []string) error {
if len(args) != n {
return fmt.Errorf("accepts %d arg(s), received %d", n, len(args))
}
return nil
}
}
func MinimumNArgs(n int) PositionalArgs {
return func(_ *Command, args []string) error {
if len(args) < n {
return fmt.Errorf("requires at least %d arg(s), only received %d", n, len(args))
}
return nil
}
}
func RangeArgs(minimum, maximum int) PositionalArgs {
return func(_ *Command, args []string) error {
if len(args) < minimum || len(args) > maximum {
return fmt.Errorf("accepts between %d and %d arg(s), received %d", minimum, maximum, len(args))
}
return nil
}
}
func OnlyValidArgs(cmd *Command, args []string) error {
if len(cmd.ValidArgs) == 0 {
return nil
}
for _, arg := range args {
if !contains(cmd.ValidArgs, arg) {
return fmt.Errorf("invalid argument %q for %q", arg, cmd.CommandPath())
}
}
return nil
}
func contains(values []string, value string) bool {
return slices.Contains(values, value)
}
+133
View File
@@ -0,0 +1,133 @@
package cmdtree
import (
"testing"
"github.com/stretchr/testify/assert"
)
func testTree() (root *Command, ran *string) {
ran = new(string)
root = &Command{
Use: "root",
Run: func(_ *Command, _ []string) { *ran = "root" },
}
child := &Command{
Use: "child",
Run: func(_ *Command, args []string) { *ran = "child:" + join(args) },
}
grandchild := &Command{
Use: "grandchild",
Run: func(_ *Command, args []string) { *ran = "grandchild:" + join(args) },
}
root.AddCommand(child)
child.AddCommand(grandchild)
return root, ran
}
func join(args []string) string {
result := ""
for i, arg := range args {
if i > 0 {
result += " "
}
result += arg
}
return result
}
func TestRouting(t *testing.T) {
root, ran := testTree()
root.SetArgs([]string{"child", "grandchild", "pos"})
assert.NoError(t, root.Execute())
assert.Equal(t, "grandchild:pos", *ran)
}
func TestRoutingWithFlagBeforeCommand(t *testing.T) {
root, ran := testTree()
var config string
root.PersistentFlags().StringVarP(&config, "config", "c", "", "")
root.SetArgs([]string{"--config", "x", "child", "positional"})
assert.NoError(t, root.Execute())
assert.Equal(t, "child:positional", *ran)
assert.Equal(t, "x", config)
}
func TestUnknownCommand(t *testing.T) {
root, _ := testTree()
root.SetArgs([]string{"bogus"})
assert.EqualError(t, root.Execute(), `unknown command "bogus" for "root"`)
}
func TestPersistentFlagInheritance(t *testing.T) {
root, _ := testTree()
var trace bool
root.PersistentFlags().BoolVar(&trace, "trace", false, "")
root.SetArgs([]string{"child", "--trace"})
assert.NoError(t, root.Execute())
assert.True(t, trace)
}
func TestPersistentHooksRunOnce(t *testing.T) {
var order []string
root := &Command{
Use: "root",
PersistentPreRun: func(_ *Command, _ []string) { order = append(order, "pre") },
PersistentPostRun: func(_ *Command, _ []string) {
order = append(order, "post")
},
}
child := &Command{
Use: "child",
Run: func(_ *Command, _ []string) { order = append(order, "run") },
}
root.AddCommand(child)
root.SetArgs([]string{"child"})
assert.NoError(t, root.Execute())
assert.Equal(t, []string{"pre", "run", "post"}, order)
}
func TestArgsValidators(t *testing.T) {
cmd := &Command{Use: "cmd"}
assert.NoError(t, NoArgs(cmd, nil))
assert.EqualError(t, NoArgs(cmd, []string{"x"}), `unknown command "x" for "cmd"`)
assert.NoError(t, ExactArgs(1)(cmd, []string{"a"}))
assert.EqualError(t, ExactArgs(1)(cmd, nil), "accepts 1 arg(s), received 0")
assert.EqualError(t, MinimumNArgs(1)(cmd, nil), "requires at least 1 arg(s), only received 0")
assert.EqualError(t, RangeArgs(1, 2)(cmd, nil), "accepts between 1 and 2 arg(s), received 0")
cmd.ValidArgs = []string{"get", "set"}
assert.NoError(t, OnlyValidArgs(cmd, []string{"get"}))
assert.EqualError(t, OnlyValidArgs(cmd, []string{"bogus"}), `invalid argument "bogus" for "cmd"`)
}
func TestShorthandShadowing(t *testing.T) {
// print defines -s stack-count while root defines -s shell; the
// subcommand's local flag must win
root := &Command{Use: "root"}
var shell string
root.Flags().StringVarP(&shell, "shell", "s", "", "")
var stack int
child := &Command{Use: "child", Run: func(_ *Command, _ []string) {}}
child.Flags().IntVarP(&stack, "stack-count", "s", 0, "")
root.AddCommand(child)
root.SetArgs([]string{"child", "-s", "3"})
assert.NoError(t, root.Execute())
assert.Equal(t, 3, stack)
assert.Equal(t, "", shell)
}
+5
View File
@@ -0,0 +1,5 @@
//go:build !windows
package cmdtree
func checkExplorerLaunch() {}
+63
View File
@@ -0,0 +1,63 @@
//go:build windows
package cmdtree
import (
"fmt"
"os"
"strings"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
// checkExplorerLaunch detects a double-click launch from Explorer: there is
// no console to read the output, so print an explanation, linger, and exit.
func checkExplorerLaunch() {
if ExplorerLaunchHelpText == "" || !startedByExplorer() {
return
}
fmt.Println(ExplorerLaunchHelpText)
time.Sleep(5 * time.Second)
os.Exit(1)
}
func startedByExplorer() bool {
pid := windows.GetCurrentProcessId()
snapshot, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)
if err != nil {
return false
}
defer func() { _ = windows.CloseHandle(snapshot) }()
var entry windows.ProcessEntry32
entry.Size = uint32(unsafe.Sizeof(entry))
var parentID uint32
for err = windows.Process32First(snapshot, &entry); err == nil; err = windows.Process32Next(snapshot, &entry) {
if entry.ProcessID == pid {
parentID = entry.ParentProcessID
break
}
}
if parentID == 0 {
return false
}
for err = windows.Process32First(snapshot, &entry); err == nil; err = windows.Process32Next(snapshot, &entry) {
if entry.ProcessID != parentID {
continue
}
name := windows.UTF16ToString(entry.ExeFile[:])
return strings.EqualFold(name, "explorer.exe")
}
return false
}
+4 -4
View File
@@ -5,8 +5,8 @@ import (
"os"
"github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/cmdtree"
"github.com/jandedobbeleer/oh-my-posh/src/runtime"
"github.com/spf13/cobra"
)
var (
@@ -23,13 +23,13 @@ type resource interface {
Test(input string) error
}
func Command(r resource) *cobra.Command {
cmd := &cobra.Command{
func Command(r resource) *cmdtree.Command {
cmd := &cmdtree.Command{
Use: "dsc",
Short: "Manage Oh My Posh DSC (Desired State Configuration)",
Long: "Manage Oh My Posh DSC (Desired State Configuration).",
ValidArgs: []string{"get", "set", "test", "schema", "export"},
Run: func(cmd *cobra.Command, args []string) {
Run: func(cmd *cmdtree.Command, args []string) {
if len(args) == 0 {
_ = cmd.Help()
return
-3
View File
@@ -26,8 +26,6 @@ require (
github.com/lucasb-eyer/go-colorful v1.4.0
github.com/pelletier/go-toml/v2 v2.4.3
github.com/shirou/gopsutil/v4 v4.26.6
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
go.yaml.in/yaml/v3 v3.0.5
)
@@ -51,7 +49,6 @@ require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pb33f/ordered-map/v2 v2.3.1 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
-10
View File
@@ -18,7 +18,6 @@ github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPn
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -39,8 +38,6 @@ github.com/gookit/color v1.6.1 h1:KoTnDxJPRgrL0SoX0f8rCFg2zI0t4E3GZZBMo2nN8LU=
github.com/gookit/color v1.6.1/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg=
github.com/invopop/jsonschema v0.14.0/go.mod h1:ygm6C2EaVNMBDPpaPlnOA2pFAxBnxGjFlMZABxm9n2I=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -70,7 +67,6 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs=
@@ -79,11 +75,6 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
@@ -100,7 +91,6 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"path"
"strings"
"github.com/jandedobbeleer/oh-my-posh/src/cmdflag"
"github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/spf13/pflag"
yaml "go.yaml.in/yaml/v3"
)
@@ -62,7 +62,7 @@ func (a *Argocd) getConfigPath() string {
func (a *Argocd) getConfigFromOpts() string {
// don't exit/panic when encountering invalid flags
flags := pflag.NewFlagSet(os.Args[0], pflag.ContinueOnError)
flags := cmdflag.NewFlagSet(os.Args[0], cmdflag.ContinueOnError)
// ignore other valid and invalid flags
flags.ParseErrorsAllowlist.UnknownFlags = true
// only care about config