From e28d91b854f8c2f4f5fe083152737d935a48eab3 Mon Sep 17 00:00:00 2001 From: "L. Yeung" Date: Sat, 24 Aug 2024 12:19:01 +0800 Subject: [PATCH] fix(cli): correct execution logic and improve messages --- src/cli/cache.go | 22 +-------- src/cli/config.go | 14 ++++-- src/cli/config_export.go | 63 +++++++++++++------------- src/cli/edit.go | 34 ++++++++++++++ src/cli/print.go | 1 - src/runtime/environment.go | 1 - src/runtime/terminal.go | 37 +++++++++------ website/docs/configuration/segment.mdx | 6 +-- 8 files changed, 102 insertions(+), 76 deletions(-) create mode 100644 src/cli/edit.go diff --git a/src/cli/cache.go b/src/cli/cache.go index cd9023fb..1ea50543 100644 --- a/src/cli/cache.go +++ b/src/cli/cache.go @@ -3,7 +3,6 @@ package cli import ( "fmt" "os" - "os/exec" "path/filepath" "strings" @@ -50,7 +49,7 @@ You can do the following: clear(env.CachePath()) case "edit": cacheFilePath := filepath.Join(env.CachePath(), cache.FileName) - editFileWithEditor(cacheFilePath) + os.Exit(editFileWithEditor(cacheFilePath)) } }, } @@ -59,25 +58,6 @@ func init() { RootCmd.AddCommand(getCache) } -func editFileWithEditor(file string) { - editor := os.Getenv("EDITOR") - - var args []string - if strings.Contains(editor, " ") { - splitted := strings.Split(editor, " ") - editor = splitted[0] - args = splitted[1:] - } - - args = append(args, file) - cmd := exec.Command(editor, args...) - - err := cmd.Run() - if err != nil { - fmt.Println(err.Error()) - } -} - func clear(cachePath string) { // get all files in the cache directory that start with omp.cache and delete them files, err := os.ReadDir(cachePath) diff --git a/src/cli/config.go b/src/cli/config.go index 7b330c4e..2bca2323 100644 --- a/src/cli/config.go +++ b/src/cli/config.go @@ -5,16 +5,18 @@ import ( "os" "time" + "github.com/jandedobbeleer/oh-my-posh/src/runtime" + "github.com/spf13/cobra" ) // configCmd represents the config command var configCmd = &cobra.Command{ - Use: "config [export|migrate|edit]", + Use: "config edit", Short: "Interact with the config", Long: `Interact with the config. -You can export, migrate or edit the config.`, +You can export, migrate or edit the config (via the editor specified in the environment variable "EDITOR").`, ValidArgs: []string{ "export", "migrate", @@ -29,7 +31,13 @@ You can export, migrate or edit the config.`, } switch args[0] { case "edit": - editFileWithEditor(os.Getenv("POSH_THEME")) + env := &runtime.Terminal{ + CmdFlags: &runtime.Flags{ + Config: configFlag, + }, + } + env.ResolveConfigPath() + os.Exit(editFileWithEditor(env.CmdFlags.Config)) case "get": // only here for backwards compatibility fmt.Print(time.Now().UnixNano() / 1000000) diff --git a/src/cli/config_export.go b/src/cli/config_export.go index 7f3dbb03..eaf534e9 100644 --- a/src/cli/config_export.go +++ b/src/cli/config_export.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "path/filepath" - "slices" "strings" "github.com/jandedobbeleer/oh-my-posh/src/config" @@ -13,9 +12,7 @@ import ( "github.com/spf13/cobra" ) -var ( - output string -) +var output string // exportCmd represents the export command var exportCmd = &cobra.Command{ @@ -27,15 +24,21 @@ You can choose to print the output to stdout, or export your config in the forma Example usage: -> oh-my-posh config export --config ~/myconfig.omp.json - -Exports the ~/myconfig.omp.json config file and prints the result to stdout. - > oh-my-posh config export --config ~/myconfig.omp.json --format toml -Exports the ~/myconfig.omp.json config file to toml and prints the result to stdout.`, +Exports the config file "~/myconfig.omp.json" in TOML format and prints the result to stdout. + +> 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) { + if len(output) == 0 && len(format) == 0 { + // usage error + fmt.Println("neither output path nor export format is specified") + os.Exit(2) + } + env := &runtime.Terminal{ CmdFlags: &runtime.Flags{ Config: configFlag, @@ -45,15 +48,25 @@ Exports the ~/myconfig.omp.json config file to toml and prints the result to std defer env.Close() cfg := config.Load(env) - if len(output) == 0 && len(format) == 0 { - // usage error - os.Exit(2) + validateExportFormat := func() { + format = strings.ToLower(format) + switch format { + case "json", "jsonc": + format = config.JSON + case "toml", "tml": + format = config.TOML + case "yaml", "yml": + format = config.YAML + default: + formats := []string{"json", "jsonc", "toml", "tml", "yaml", "yml"} + // usage error + fmt.Printf("export format must be one of these: %s\n", strings.Join(formats, ", ")) + os.Exit(2) + } } - formats := []string{"json", "jsonc", "toml", "tml", "yaml", "yml"} - if len(format) != 0 && !slices.Contains(formats, format) { - // usage error - os.Exit(2) + if len(format) != 0 { + validateExportFormat() } if len(output) == 0 { @@ -65,18 +78,7 @@ Exports the ~/myconfig.omp.json config file to toml and prints the result to std if len(format) == 0 { format = strings.TrimPrefix(filepath.Ext(output), ".") - } - - switch format { - case "json", "jsonc": - format = config.JSON - case "toml", "tml": - format = config.TOML - case "yaml", "yml": - format = config.YAML - default: - // data error - os.Exit(65) + validateExportFormat() } cfg.Write(format) @@ -84,10 +86,7 @@ Exports the ~/myconfig.omp.json config file to toml and prints the result to std } func cleanOutputPath(path string, env runtime.Environment) string { - if strings.HasPrefix(path, "~") { - path = strings.TrimPrefix(path, "~") - path = filepath.Join(env.Home(), path) - } + path = runtime.ReplaceTildePrefixWithHomeDir(env, path) if !filepath.IsAbs(path) { if absPath, err := filepath.Abs(path); err == nil { diff --git a/src/cli/edit.go b/src/cli/edit.go new file mode 100644 index 00000000..f03227cc --- /dev/null +++ b/src/cli/edit.go @@ -0,0 +1,34 @@ +package cli + +import ( + "fmt" + "os" + "os/exec" + "strings" +) + +func editFileWithEditor(file string) int { + editor := strings.TrimSpace(os.Getenv("EDITOR")) + if len(editor) == 0 { + fmt.Println(`no editor specified in the environment variable "EDITOR"`) + return 1 + } + + var args []string + if strings.Contains(editor, " ") { + strs := strings.Split(editor, " ") + editor = strs[0] + args = strs[1:] + } + + args = append(args, file) + cmd := exec.Command(editor, args...) + + err := cmd.Run() + if err != nil { + fmt.Println(err.Error()) + return 1 + } + + return cmd.ProcessState.ExitCode() +} diff --git a/src/cli/print.go b/src/cli/print.go index 4eb31c4a..c1e118e5 100644 --- a/src/cli/print.go +++ b/src/cli/print.go @@ -66,7 +66,6 @@ var printCmd = &cobra.Command{ Plain: plain, Primary: args[0] == "primary", Cleared: cleared, - Cached: cached, NoExitCode: noStatus, Column: column, JobCount: jobCount, diff --git a/src/runtime/environment.go b/src/runtime/environment.go index 15e2bcd6..408382b1 100644 --- a/src/runtime/environment.go +++ b/src/runtime/environment.go @@ -98,7 +98,6 @@ type Flags struct { HasTransient bool PromptCount int Cleared bool - Cached bool NoExitCode bool Column int JobCount int diff --git a/src/runtime/terminal.go b/src/runtime/terminal.go index 3a8313b8..2fd9e09d 100644 --- a/src/runtime/terminal.go +++ b/src/runtime/terminal.go @@ -74,7 +74,7 @@ func (term *Terminal) Init() { term.deviceCache = initCache(cache.FileName) term.sessionCache = initCache(cache.SessionFileName) - term.resolveConfigPath() + term.ResolveConfigPath() term.cmdCache = &cache.Command{ Commands: maps.NewConcurrent(), @@ -82,12 +82,10 @@ func (term *Terminal) Init() { term.tmplCache = &cache.Template{} - if !term.CmdFlags.Cached { - term.SetPromptCount() - } + term.SetPromptCount() } -func (term *Terminal) resolveConfigPath() { +func (term *Terminal) ResolveConfigPath() { defer term.Trace(time.Now()) // if the config flag is set, we'll use that over POSH_THEME @@ -129,11 +127,7 @@ func (term *Terminal) resolveConfigPath() { return } - configFile := term.CmdFlags.Config - if strings.HasPrefix(configFile, "~") { - configFile = strings.TrimPrefix(configFile, "~") - configFile = filepath.Join(term.Home(), configFile) - } + configFile := ReplaceTildePrefixWithHomeDir(term, term.CmdFlags.Config) abs, err := filepath.Abs(configFile) if err != nil { @@ -726,11 +720,14 @@ func dirMatchesOneOf(dir, home, goos string, regexes []string) bool { } for _, element := range regexes { - normalizedElement := strings.ReplaceAll(element, "\\\\", "/") - if strings.HasPrefix(normalizedElement, "~") { - normalizedElement = strings.Replace(normalizedElement, "~", home, 1) + normalized := strings.ReplaceAll(element, "\\\\", "/") + if strings.HasPrefix(normalized, "~") { + rem := normalized[1:] + if len(rem) == 0 || rem[0] == '/' { + normalized = home + rem + } } - pattern := fmt.Sprintf("^%s$", normalizedElement) + pattern := fmt.Sprintf("^%s$", normalized) if goos == WINDOWS || goos == DARWIN { pattern = "(?i)" + pattern } @@ -843,9 +840,19 @@ func Base(env Environment, path string) string { return path } +func ReplaceTildePrefixWithHomeDir(env Environment, path string) string { + if !strings.HasPrefix(path, "~") { + return path + } + rem := path[1:] + if len(rem) == 0 || IsPathSeparator(env, rem[0]) { + return env.Home() + rem + } + return path +} + func ReplaceHomeDirPrefixWithTilde(env Environment, path string) string { home := env.Home() - // match Home directory exactly if !strings.HasPrefix(path, home) { return path } diff --git a/website/docs/configuration/segment.mdx b/website/docs/configuration/segment.mdx index d80fb2bf..50e0db81 100644 --- a/website/docs/configuration/segment.mdx +++ b/website/docs/configuration/segment.mdx @@ -141,7 +141,7 @@ will not be rendered when in one of the excluded locations. ``` The strings specified in these properties are evaluated as [regular expressions][regex]. You -can use any valid regular expression construct, but the regular expression must match the entire directory +can use any valid regular expression construct, but the regular expression must match the **ENTIRE** directory name. The following will match `/Users/posh/Projects/Foo` but not `/home/Users/posh/Projects/Foo`. ```json @@ -165,8 +165,8 @@ You can also combine these properties: - Oh My Posh will accept both `/` and `\` as path separators for a folder and will match regardless of which is used by the current operating system. -- Because the strings are evaluated as regular expressions, if you want to use a `\` in a Windows - directory name, you need to specify it as `\\\\`. +- Because the strings are evaluated as regular expressions, if you want to use a backslash (`\`) in a Windows + directory name, you need to specify it as double backslashes, and if using JSON format you should escape it as `\\\\`. - The character `~` at the start of a specified folder will match the user's home directory. - The comparison is case-insensitive on Windows and macOS, but case-sensitive on other operating systems.