Repository: JanDeDobbeleer/oh-my-posh
Pull request #2260 (2022-05-12 18:18:01)

feat(path): template for path separator
### Prerequisites

- [x] I have read and understood the [contributing guide][CONTRIBUTING.md]
- [x] The commit message follows the [conventional commits][cc] guidelines
- [x] Tests for the changes have been added (for bug fixes/features)
- [x] Docs have been added/updated (for bug fixes/features)

### Description

- feat(debug): expose Log via environment
- feat(path): template for path separator


<!---

Tips:

If you're not comfortable with working with Git, we're working a guide (https://ohmyposh.dev/docs/contributing_git) to help you out.
Oh My Posh advises GitKraken (https://www.gitkraken.com/invite/nQmDPR9D) as your preferred cross platform Git GUI power tool.

-->

[CONTRIBUTING.md]: https://github.com/JanDeDobbeleer/oh-my-posh/blob/main/CONTRIBUTING.md
[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary



Patch:
diff --git a/docs/docs/segments/path.md b/docs/docs/segments/path.md
index 33bb3633..78a8aab9 100644
--- a/docs/docs/segments/path.md
+++ b/docs/docs/segments/path.md
@@ -29,6 +29,7 @@ Display the current path.
 ## Properties
 
 - folder_separator_icon: `string` - the symbol to use as a separator between folders - defaults to platform path separator
+- folder_separator_template: `string` - the [template][templates] to use as a separator between folders - defaults to ``
 - home_icon: `string` - the icon to display when at `$HOME` - defaults to `~`
 - folder_icon: `string` - the icon to use as a folder indication - defaults to `..`
 - windows_registry_icon: `string` - the icon to display when in the Windows registry - defaults to `\uE0B1`
diff --git a/src/environment/darwin.go b/src/environment/darwin.go
index b5716c05..d7788c5d 100644
--- a/src/environment/darwin.go
+++ b/src/environment/darwin.go
@@ -35,13 +35,13 @@ func (env *ShellEnvironment) parseBatteryOutput(output string) (*BatteryInfo, er
 	matches := regex.FindNamedRegexMatch(`(?P<PERCENTAGE>[0-9]{1,3})%; (?P<STATE>[a-zA-Z\s]+);`, output)
 	if len(matches) != 2 {
 		msg := "Unable to find battery state based on output"
-		env.log(Error, "BatteryInfo", msg)
+		env.Log(Error, "BatteryInfo", msg)
 		return nil, errors.New(msg)
 	}
 	var percentage int
 	var err error
 	if percentage, err = strconv.Atoi(matches["PERCENTAGE"]); err != nil {
-		env.log(Error, "BatteryInfo", err.Error())
+		env.Log(Error, "BatteryInfo", err.Error())
 		return nil, errors.New("Unable to parse battery percentage")
 	}
 	return &BatteryInfo{
@@ -54,7 +54,7 @@ func (env *ShellEnvironment) BatteryState() (*BatteryInfo, error) {
 	defer env.Trace(time.Now(), "BatteryInfo")
 	output, err := env.RunCommand("pmset", "-g", "batt")
 	if err != nil {
-		env.log(Error, "BatteryInfo", err.Error())
+		env.Log(Error, "BatteryInfo", err.Error())
 		return nil, err
 	}
 	if !strings.Contains(output, "Battery") {
diff --git a/src/environment/shell.go b/src/environment/shell.go
index 73654855..d2462d77 100644
--- a/src/environment/shell.go
+++ b/src/environment/shell.go
@@ -189,6 +189,7 @@ type Environment interface {
 	ConvertToWindowsPath(path string) string
 	WifiNetwork() (*WifiInfo, error)
 	TemplateCache() *TemplateCache
+	Log(logType LogType, funcName, message string)
 	Trace(start time.Time, function string, args ...string)
 }
 
@@ -209,11 +210,11 @@ func (c *commandCache) get(command string) (string, bool) {
 	return command, ok
 }
 
-type logType string
+type LogType string
 
 const (
-	Error logType = "error"
-	Debug logType = "debug"
+	Error LogType = "error"
+	Debug LogType = "debug"
 )
 
 type ShellEnvironment struct {
@@ -305,11 +306,11 @@ func (env *ShellEnvironment) Trace(start time.Time, function string, args ...str
 	log.Println(trace)
 }
 
-func (env *ShellEnvironment) log(lt logType, function, message string) {
+func (env *ShellEnvironment) Log(logType LogType, funcName, message string) {
 	if !env.CmdFlags.Debug {
 		return
 	}
-	trace := fmt.Sprintf("%s: %s\n%s", lt, function, message)
+	trace := fmt.Sprintf("%s: %s\n%s", logType, funcName, message)
 	log.Println(trace)
 }
 
@@ -324,7 +325,7 @@ func (env *ShellEnvironment) debugF(function string, fn func() string) {
 func (env *ShellEnvironment) Getenv(key string) string {
 	defer env.Trace(time.Now(), "Getenv", key)
 	val := os.Getenv(key)
-	env.log(Debug, "Getenv", val)
+	env.Log(Debug, "Getenv", val)
 	return val
 }
 
@@ -333,7 +334,7 @@ func (env *ShellEnvironment) Pwd() string {
 	lock.Lock()
 	defer func() {
 		lock.Unlock()
-		env.log(Debug, "Pwd", env.cwd)
+		env.Log(Debug, "Pwd", env.cwd)
 	}()
 	if env.cwd != "" {
 		return env.cwd
@@ -349,7 +350,7 @@ func (env *ShellEnvironment) Pwd() string {
 	}
 	dir, err := os.Getwd()
 	if err != nil {
-		env.log(Error, "Pwd", err.Error())
+		env.Log(Error, "Pwd", err.Error())
 		return ""
 	}
 	env.cwd = correctPath(dir)
@@ -362,7 +363,7 @@ func (env *ShellEnvironment) HasFiles(pattern string) bool {
 	pattern = cwd + env.PathSeparator() + pattern
 	matches, err := filepath.Glob(pattern)
 	if err != nil {
-		env.log(Error, "HasFiles", err.Error())
+		env.Log(Error, "HasFiles", err.Error())
 		return false
 	}
 	for _, match := range matches {
@@ -370,10 +371,10 @@ func (env *ShellEnvironment) HasFiles(pattern string) bool {
 		if f.IsDir() {
 			continue
 		}
-		env.log(Debug, "HasFiles", "true")
+		env.Log(Debug, "HasFiles", "true")
 		return true
 	}
-	env.log(Debug, "HasFiles", "false")
+	env.Log(Debug, "HasFiles", "false")
 	return false
 }
 
@@ -382,7 +383,7 @@ func (env *ShellEnvironment) HasFilesInDir(dir, pattern string) bool {
 	pattern = dir + env.PathSeparator() + pattern
 	matches, err := filepath.Glob(pattern)
 	if err != nil {
-		env.log(Error, "HasFilesInDir", err.Error())
+		env.Log(Error, "HasFilesInDir", err.Error())
 		return false
 	}
 	hasFilesInDir := len(matches) > 0
@@ -396,18 +397,18 @@ func (env *ShellEnvironment) HasFileInParentDirs(pattern string, depth uint) boo
 
 	for c := 0; c < int(depth); c++ {
 		if env.HasFilesInDir(currentFolder, pattern) {
-			env.log(Debug, "HasFileInParentDirs", "true")
+			env.Log(Debug, "HasFileInParentDirs", "true")
 			return true
 		}
 
 		if dir := filepath.Dir(currentFolder); dir != currentFolder {
 			currentFolder = dir
 		} else {
-			env.log(Debug, "HasFileInParentDirs", "false")
+			env.Log(Debug, "HasFileInParentDirs", "false")
 			return false
 		}
 	}
-	env.log(Debug, "HasFileInParentDirs", "false")
+	env.Log(Debug, "HasFileInParentDirs", "false")
 	return false
 }
 
@@ -415,7 +416,7 @@ func (env *ShellEnvironment) HasFolder(folder string) bool {
 	defer env.Trace(time.Now(), "HasFolder", folder)
 	f, err := os.Stat(folder)
 	if err != nil {
-		env.log(Debug, "HasFolder", "false")
+		env.Log(Debug, "HasFolder", "false")
 		return false
 	}
 	env.debugF("HasFolder", func() string { return strconv.FormatBool(f.IsDir()) })
@@ -433,11 +434,11 @@ func (env *ShellEnvironment) FileContent(file string) string {
 	}
 	content, err := ioutil.ReadFile(file)
 	if err != nil {
-		env.log(Error, "FileContent", err.Error())
+		env.Log(Error, "FileContent", err.Error())
 		return ""
 	}
 	fileContent := string(content)
-	env.log(Debug, "FileContent", fileContent)
+	env.Log(Debug, "FileContent", fileContent)
 	return fileContent
 }
 
@@ -445,7 +446,7 @@ func (env *ShellEnvironment) LsDir(path string) []fs.DirEntry {
 	defer env.Trace(time.Now(), "LsDir", path)
 	entries, err := os.ReadDir(path)
 	if err != nil {
-		env.log(Error, "LsDir", err.Error())
+		env.Log(Error, "LsDir", err.Error())
 		return nil
 	}
 	env.debugF("LsDir", func() string {
@@ -469,7 +470,7 @@ func (env *ShellEnvironment) User() string {
 	if user == "" {
 		user = os.Getenv("USERNAME")
 	}
-	env.log(Debug, "User", user)
+	env.Log(Debug, "User", user)
 	return user
 }
 
@@ -477,11 +478,11 @@ func (env *ShellEnvironment) Host() (string, error) {
 	defer env.Trace(time.Now(), "Host")
 	hostName, err := os.Hostname()
 	if err != nil {
-		env.log(Error, "Host", err.Error())
+		env.Log(Error, "Host", err.Error())
 		return "", err
 	}
 	hostName = cleanHostName(hostName)
-	env.log(Debug, "Host", hostName)
+	env.Log(Debug, "Host", hostName)
 	return hostName, nil
 }
 
@@ -504,7 +505,7 @@ func (env *ShellEnvironment) RunCommand(command string, args ...string) (string,
 	if cmdErr != nil {
 		output := err.String()
 		errorStr := fmt.Sprintf("cmd.Start() failed with '%s'", output)
-		env.log(Error, "RunCommand", errorStr)
+		env.Log(Error, "RunCommand", errorStr)
 		return output, cmdErr
 	}
 	// some silly commands return 0 and the output is in stderr instead of stdout
@@ -513,7 +514,7 @@ func (env *ShellEnvironment) RunCommand(command string, args ...string) (string,
 		result = err.String()
 	}
 	output := strings.TrimSpace(result)
-	env.log(Debug, "RunCommand", output)
+	env.Log(Debug, "RunCommand", output)
 	return output, nil
 }
 
@@ -540,7 +541,7 @@ func (env *ShellEnvironment) CommandPath(command string) string {
 		env.cmdCache.set(command, path)
 		return path
 	}
-	env.log(Error, "CommandPath", err.Error())
+	env.Log(Error, "CommandPath", err.Error())
 	return ""
 }
 
@@ -578,7 +579,7 @@ func (env *ShellEnvironment) Shell() string {
 	p, _ := process.NewProcess(int32(pid))
 	name, err := p.Name()
 	if err != nil {
-		env.log(Error, "Shell", err.Error())
+		env.Log(Error, "Shell", err.Error())
 		return Unknown
 	}
 	if name == "cmd.exe" {
@@ -586,7 +587,7 @@ func (env *ShellEnvironment) Shell() string {
 		name, err = p.Name()
 	}
 	if err != nil {
-		env.log(Error, "Shell", err.Error())
+		env.Log(Error, "Shell", err.Error())
 		return Unknown
 	}
 	// Cache the shell value to speed things up.
@@ -607,23 +608,23 @@ func (env *ShellEnvironment) HTTPRequest(targetURL string, timeout int, requestM
 	}
 	response, err := client.Do(request)
 	if err != nil {
-		env.log(Error, "HTTPRequest", err.Error())
+		env.Log(Error, "HTTPRequest", err.Error())
 		return nil, err
 	}
 	// anything inside the range [200, 299] is considered a success
 	if response.StatusCode < 200 || response.StatusCode >= 300 {
 		message := "HTTP status code " + strconv.Itoa(response.StatusCode)
 		err := errors.New(message)
-		env.log(Error, "HTTPRequest", message)
+		env.Log(Error, "HTTPRequest", message)
 		return nil, err
 	}
 	defer response.Body.Close()
 	body, err := ioutil.ReadAll(response.Body)
 	if err != nil {
-		env.log(Error, "HTTPRequest", err.Error())
+		env.Log(Error, "HTTPRequest", err.Error())
 		return nil, err
 	}
-	env.log(Debug, "HTTPRequest", string(body))
+	env.Log(Debug, "HTTPRequest", string(body))
 	return body, nil
 }
 
@@ -647,7 +648,7 @@ func (env *ShellEnvironment) HasParentFilePath(path string) (*FileInfo, error) {
 			currentFolder = dir
 			continue
 		}
-		env.log(Error, "HasParentFilePath", err.Error())
+		env.Log(Error, "HasParentFilePath", err.Error())
 		return nil, errors.New("no match at root level")
 	}
 }
@@ -722,7 +723,7 @@ func (env *ShellEnvironment) DirMatchesOneOf(dir string, regexes []string) (matc
 	defer func() {
 		if err := recover(); err != nil {
 			message := fmt.Sprintf("%s", err)
-			env.log(Error, "DirMatchesOneOf", message)
+			env.Log(Error, "DirMatchesOneOf", message)
 			match = false
 		}
 	}()
diff --git a/src/environment/unix.go b/src/environment/unix.go
index 210d3d9a..6e7b3d07 100644
--- a/src/environment/unix.go
+++ b/src/environment/unix.go
@@ -50,7 +50,7 @@ func (env *ShellEnvironment) TerminalWidth() (int, error) {
 	}
 	width, err := terminal.Width()
 	if err != nil {
-		env.log(Error, "TerminalWidth", err.Error())
+		env.Log(Error, "TerminalWidth", err.Error())
 	}
 	return int(width), err
 }
diff --git a/src/environment/windows.go b/src/environment/windows.go
index 067f9178..ce1c7574 100644
--- a/src/environment/windows.go
+++ b/src/environment/windows.go
@@ -33,7 +33,7 @@ func (env *ShellEnvironment) Root() bool {
 		0, 0, 0, 0, 0, 0,
 		&sid)
 	if err != nil {
-		env.log(Error, "Root", err.Error())
+		env.Log(Error, "Root", err.Error())
 		return false
 	}
 	defer func() {
@@ -47,7 +47,7 @@ func (env *ShellEnvironment) Root() bool {
 
 	member, err := token.IsMember(sid)
 	if err != nil {
-		env.log(Error, "Root", err.Error())
+		env.Log(Error, "Root", err.Error())
 		return false
 	}
 
@@ -57,7 +57,7 @@ func (env *ShellEnvironment) Root() bool {
 func (env *ShellEnvironment) Home() string {
 	home := os.Getenv("HOME")
 	defer func() {
-		env.log(Debug, "Home", home)
+		env.Log(Debug, "Home", home)
 	}()
 	if len(home) > 0 {
 		return home
@@ -74,7 +74,7 @@ func (env *ShellEnvironment) QueryWindowTitles(processName, windowTitleRegex str
 	defer env.Trace(time.Now(), "WindowTitle", windowTitleRegex)
 	title, err := queryWindowTitles(processName, windowTitleRegex)
 	if err != nil {
-		env.log(Error, "QueryWindowTitles", err.Error())
+		env.Log(Error, "QueryWindowTitles", err.Error())
 	}
 	return title, err
 }
@@ -96,12 +96,12 @@ func (env *ShellEnvironment) TerminalWidth() (int, error) {
 	}
 	handle, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
 	if err != nil {
-		env.log(Error, "TerminalWidth", err.Error())
+		env.Log(Error, "TerminalWidth", err.Error())
 		return 0, err
 	}
 	info, err := winterm.GetConsoleScreenBufferInfo(uintptr(handle))
 	if err != nil {
-		env.log(Error, "TerminalWidth", err.Error())
+		env.Log(Error, "TerminalWidth", err.Error())
 		return 0, err
 	}
 	// return int(float64(info.Size.X) * 0.57), nil
@@ -163,14 +163,14 @@ func (env *ShellEnvironment) WindowsRegistryKeyValue(path string) (*WindowsRegis
 
 	if len(regPathParts) < 2 {
 		errorLogMsg := fmt.Sprintf("Error, malformed registry path: '%s'", path)
-		env.log(Error, "WindowsRegistryKeyValue", errorLogMsg)
+		env.Log(Error, "WindowsRegistryKeyValue", errorLogMsg)
 		return nil, errors.New(errorLogMsg)
 	}
 
 	regRootHKeyHandle := getHKEYHandleFromAbbrString(regPathParts[0])
 	if regRootHKeyHandle == 0 {
 		errorLogMsg := fmt.Sprintf("Error, Supplied root HKEY value not valid: '%s'", regPathParts[0])
-		env.log(Error, "WindowsRegistryKeyValue", errorLogMsg)
+		env.Log(Error, "WindowsRegistryKeyValue", errorLogMsg)
 		return nil, errors.New(errorLogMsg)
 	}
 
@@ -179,7 +179,7 @@ func (env *ShellEnvironment) WindowsRegistryKeyValue(path string) (*WindowsRegis
 
 	if lastSlash < 0 {
 		errorLogMsg := fmt.Sprintf("Error, malformed registry path: '%s'", path)
-		env.log(Error, "WindowsRegistryKeyValue", errorLogMsg)
+		env.Log(Error, "WindowsRegistryKeyValue", errorLogMsg)
 		return nil, errors.New(errorLogMsg)
 	}
 
@@ -191,13 +191,13 @@ func (env *ShellEnvironment) WindowsRegistryKeyValue(path string) (*WindowsRegis
 	if len(regKeyLogged) == 0 {
 		regKeyLogged = "(Default)"
 	}
-	env.log(Debug, "WindowsRegistryKeyValue", fmt.Sprintf("WindowsRegistryKeyValue: root:\"%s\", path:\"%s\", key:\"%s\"", regPathParts[0], regPath, regKeyLogged))
+	env.Log(Debug, "WindowsRegistryKeyValue", fmt.Sprintf("WindowsRegistryKeyValue: root:\"%s\", path:\"%s\", key:\"%s\"", regPathParts[0], regPath, regKeyLogged))
 
 	// Second part of split is registry path after HK part - needs to be UTF16 to pass to the windows. API
 	regPathUTF16, err := windows.UTF16FromString(regPath)
 	if err != nil {
 		errorLogMsg := fmt.Sprintf("Error, Could not convert supplied path '%s' to UTF16, error: '%s'", regPath, err)
-		env.log(Error, "WindowsRegistryKeyValue", errorLogMsg)
+		env.Log(Error, "WindowsRegistryKeyValue", errorLogMsg)
 		return nil, errors.New(errorLogMsg)
 	}
 
@@ -206,14 +206,14 @@ func (env *ShellEnvironment) WindowsRegistryKeyValue(path string) (*WindowsRegis
 	regOpenErr := windows.RegOpenKeyEx(regRootHKeyHandle, &regPathUTF16[0], 0, windows.KEY_READ, &hKeyHandle)
 	if regOpenErr != nil {
 		errorLogMsg := fmt.Sprintf("Error RegOpenKeyEx opening registry path to '%s', error: '%s'", regPath, regOpenErr)
-		env.log(Error, "WindowsRegistryKeyValue", errorLogMsg)
+		env.Log(Error, "WindowsRegistryKeyValue", errorLogMsg)
 		return nil, errors.New(errorLogMsg)
 	}
 	// Success - from here on out, when returning make sure to close that reg key with a deferred call to close:
 	defer func() {
 		err := windows.RegCloseKey(hKeyHandle)
 		if err != nil {
-			env.log(Error, "WindowsRegistryKeyValue", fmt.Sprintf("Error closing registry key: %s", err))
+			env.Log(Error, "WindowsRegistryKeyValue", fmt.Sprintf("Error closing registry key: %s", err))
 		}
 	}()
 
@@ -221,7 +221,7 @@ func (env *ShellEnvironment) WindowsRegistryKeyValue(path string) (*WindowsRegis
 	regKeyUTF16, err := windows.UTF16FromString(regKey)
 	if err != nil {
 		errorLogMsg := fmt.Sprintf("Error, could not convert supplied key '%s' to UTF16, error: '%s'", regKey, err)
-		env.log(Error, "WindowsRegistryKeyValue", errorLogMsg)
+		env.Log(Error, "WindowsRegistryKeyValue", errorLogMsg)
 		return nil, errors.New(errorLogMsg)
 	}
 
@@ -232,7 +232,7 @@ func (env *ShellEnvironment) WindowsRegistryKeyValue(path string) (*WindowsRegis
 	regQueryErr := windows.RegQueryValueEx(hKeyHandle, &regKeyUTF16[0], nil, &keyBufType, nil, &keyBufSize)
 	if regQueryErr != nil {
 		errorLogMsg := fmt.Sprintf("Error calling RegQueryValueEx to retrieve key data size with error '%s'", regQueryErr)
-		env.log(Error, "WindowsRegistryKeyValue", errorLogMsg)
+		env.Log(Error, "WindowsRegistryKeyValue", errorLogMsg)
 		return nil, errors.New(errorLogMsg)
 	}
 
@@ -242,7 +242,7 @@ func (env *ShellEnvironment) WindowsRegistryKeyValue(path string) (*WindowsRegis
 	regQueryErr = windows.RegQueryValueEx(hKeyHandle, &regKeyUTF16[0], nil, &keyBufType, &keyBuf[0], &keyBufSize)
 	if regQueryErr != nil {
 		errorLogMsg := fmt.Sprintf("Error calling RegQueryValueEx to retrieve key data with error '%s'", regQueryErr)
-		env.log(Error, "WindowsRegistryKeyValue", errorLogMsg)
+		env.Log(Error, "WindowsRegistryKeyValue", errorLogMsg)
 		return nil, errors.New(errorLogMsg)
 	}
 
@@ -253,20 +253,20 @@ func (env *ShellEnvironment) WindowsRegistryKeyValue(path string) (*WindowsRegis
 		uint16p = (*uint16)(unsafe.Pointer(&keyBuf[0])) // nasty casty
 
 		valueString := windows.UTF16PtrToString(uint16p)
-		env.log(Debug, "WindowsRegistryKeyValue", fmt.Sprintf("success, string: %s", valueString))
+		env.Log(Debug, "WindowsRegistryKeyValue", fmt.Sprintf("success, string: %s", valueString))
 
 		return &WindowsRegistryValue{ValueType: RegString, Str: valueString}, nil
 	case windows.REG_DWORD:
 		var uint32p *uint32
 		uint32p = (*uint32)(unsafe.Pointer(&keyBuf[0])) // more casting goodness
 
-		env.log(Debug, "WindowsRegistryKeyValue", fmt.Sprintf("success, DWORD, 0x%08X", *uint32p))
+		env.Log(Debug, "WindowsRegistryKeyValue", fmt.Sprintf("success, DWORD, 0x%08X", *uint32p))
 		return &WindowsRegistryValue{ValueType: RegDword, Dword: *uint32p}, nil
 	case windows.REG_QWORD:
 		var uint64p *uint64
 		uint64p = (*uint64)(unsafe.Pointer(&keyBuf[0])) // more casting goodness
 
-		env.log(Debug, "WindowsRegistryKeyValue", fmt.Sprintf("success, QWORD, 0x%016X", *uint64p))
+		env.Log(Debug, "WindowsRegistryKeyValue", fmt.Sprintf("success, QWORD, 0x%016X", *uint64p))
 		return &WindowsRegistryValue{ValueType: RegQword, Qword: *uint64p}, nil
 	default:
 		errorLogMsg := fmt.Sprintf("Error, no formatter for REG_? type:%d, data size:%d bytes", keyBufType, keyBufSize)
@@ -375,7 +375,7 @@ func (env *ShellEnvironment) parseNetworkInterface(network *WLAN_INTERFACE_INFO,
 		uintptr(unsafe.Pointer(&wlanAttr)),
 		uintptr(unsafe.Pointer(nil)))
 	if e != 0 {
-		env.log(Error, "parseNetworkInterface", "wlan_intf_opcode_current_connection error")
+		env.Log(Error, "parseNetworkInterface", "wlan_intf_opcode_current_connection error")
 		return &info, err
 	}
 
@@ -432,7 +432,7 @@ func (env *ShellEnvironment) parseNetworkInterface(network *WLAN_INTERFACE_INFO,
 		uintptr(unsafe.Pointer(&channel)),
 		uintptr(unsafe.Pointer(nil)))
 	if e != 0 {
-		env.log(Error, "parseNetworkInterface", "wlan_intf_opcode_channel_number error")
+		env.Log(Error, "parseNetworkInterface", "wlan_intf_opcode_channel_number error")
 		return &info, err
 	}
 	info.Channel = int(*channel)
diff --git a/src/environment/windows_unix.go b/src/environment/windows_unix.go
index 5ac7a5c0..6601c561 100644
--- a/src/environment/windows_unix.go
+++ b/src/environment/windows_unix.go
@@ -50,7 +50,7 @@ func (env *ShellEnvironment) BatteryState() (*BatteryInfo, error) {
 	batteries, err := battery.GetAll()
 	// actual error, return it
 	if err != nil && len(batteries) == 0 {
-		env.log(Error, "BatteryInfo", err.Error())
+		env.Log(Error, "BatteryInfo", err.Error())
 		return nil, err
 	}
 	// there are no batteries found
@@ -98,7 +98,7 @@ func (env *ShellEnvironment) BatteryState() (*BatteryInfo, error) {
 	}
 	// another error occurred (possibly unmapped use-case), return it
 	if fatalErr != nil {
-		env.log(Error, "BatteryInfo", fatalErr.Error())
+		env.Log(Error, "BatteryInfo", fatalErr.Error())
 		return nil, fatalErr
 	}
 	// everything is fine
diff --git a/src/mock/environment.go b/src/mock/environment.go
index 211d240f..7af47785 100644
--- a/src/mock/environment.go
+++ b/src/mock/environment.go
@@ -234,3 +234,7 @@ func (env *MockedEnvironment) DirMatchesOneOf(dir string, regexes []string) bool
 func (env *MockedEnvironment) Trace(start time.Time, function string, args ...string) {
 	_ = env.Called(start, function, args)
 }
+
+func (env *MockedEnvironment) Log(logType environment.LogType, funcName, message string) {
+	_ = env.Called(logType, funcName, message)
+}
diff --git a/src/segments/path.go b/src/segments/path.go
index d8146803..ba0f7fca 100644
--- a/src/segments/path.go
+++ b/src/segments/path.go
@@ -5,6 +5,7 @@ import (
 	"oh-my-posh/environment"
 	"oh-my-posh/properties"
 	"oh-my-posh/regex"
+	"oh-my-posh/template"
 	"sort"
 	"strings"
 )
@@ -22,6 +23,8 @@ type Path struct {
 const (
 	// FolderSeparatorIcon the path which is split will be separated by this icon
 	FolderSeparatorIcon properties.Property = "folder_separator_icon"
+	// FolderSeparatorTemplate the path which is split will be separated by this template
+	FolderSeparatorTemplate properties.Property = "folder_separator_template"
 	// HomeIcon indicates the $HOME location
 	HomeIcon properties.Property = "home_icon"
 	// FolderIcon identifies one folder
@@ -114,6 +117,23 @@ func (pt *Path) Init(props properties.Properties, env environment.Environment) {
 	pt.env = env
 }
 
+func (pt *Path) getFolderSeparator() string {
+	separatorTemplate := pt.props.GetString(FolderSeparatorTemplate, "")
+	if len(separatorTemplate) == 0 {
+		return pt.props.GetString(FolderSeparatorIcon, pt.env.PathSeparator())
+	}
+	tmpl := &template.Text{
+		Template: separatorTemplate,
+		Context:  pt,
+		Env:      pt.env,
+	}
+	text, err := tmpl.Render()
+	if err != nil {
+		pt.env.Log(environment.Error, "getFolderSeparator", err.Error())
+	}
+	return text
+}
+
 func (pt *Path) getMixedPath() string {
 	var buffer strings.Builder
 	pwd := pt.getPwd()
@@ -128,7 +148,7 @@ func (pt *Path) getMixedPath() string {
 		if len(part) > threshold && i != 0 && i != len(splitted)-1 {
 			folder = pt.props.GetString(FolderIcon, "..")
 		}
-		separator := pt.props.GetString(FolderSeparatorIcon, pt.env.PathSeparator())
+		separator := pt.getFolderSeparator()
 		if i == 0 {
 			separator = ""
 		}
@@ -144,7 +164,7 @@ func (pt *Path) getAgnosterPath() string {
 	buffer.WriteString(pt.rootLocation())
 	pathDepth := pt.pathDepth(pwd)
 	folderIcon := pt.props.GetString(FolderIcon, "..")
-	separator := pt.props.GetString(FolderSeparatorIcon, pt.env.PathSeparator())
+	separator := pt.getFolderSeparator()
 	for i := 1; i < pathDepth; i++ {
 		buffer.WriteString(fmt.Sprintf("%s%s", separator, folderIcon))
 	}
@@ -160,7 +180,7 @@ func (pt *Path) getAgnosterLeftPath() string {
 	pwd = strings.Trim(pwd, separator)
 	splitted := strings.Split(pwd, separator)
 	folderIcon := pt.props.GetString(FolderIcon, "..")
-	separator = pt.props.GetString(FolderSeparatorIcon, separator)
+	separator = pt.getFolderSeparator()
 	switch len(splitted) {
 	case 0:
 		return ""
@@ -194,7 +214,7 @@ func (pt *Path) getLetterPath() string {
 	var buffer strings.Builder
 	pwd := pt.getPwd()
 	splitted := strings.Split(pwd, pt.env.PathSeparator())
-	separator := pt.props.GetString(FolderSeparatorIcon, pt.env.PathSeparator())
+	separator := pt.getFolderSeparator()
 	for i := 0; i < len(splitted)-1; i++ {
 		folder := splitted[i]
 		if len(folder) == 0 {
@@ -213,7 +233,7 @@ func (pt *Path) getUniqueLettersPath() string {
 	var buffer strings.Builder
 	pwd := pt.getPwd()
 	splitted := strings.Split(pwd, pt.env.PathSeparator())
-	separator := pt.props.GetString(FolderSeparatorIcon, pt.env.PathSeparator())
+	separator := pt.getFolderSeparator()
 	letters := make(map[string]bool, len(splitted))
 	for i := 0; i < len(splitted)-1; i++ {
 		folder := splitted[i]
@@ -259,8 +279,8 @@ func (pt *Path) getAgnosterShortPath() string {
 	if pathDepth <= maxDepth {
 		return pt.getAgnosterFullPath()
 	}
+	folderSeparator := pt.getFolderSeparator()
 	pathSeparator := pt.env.PathSeparator()
-	folderSeparator := pt.props.GetString(FolderSeparatorIcon, pathSeparator)
 	splitted := strings.Split(pwd, pathSeparator)
 	fullPathDepth := len(splitted)
 	splitPos := fullPathDepth - maxDepth
@@ -357,7 +377,7 @@ func (pt *Path) replaceFolderSeparators(pwd string) string {
 	if pwd == defaultSeparator {
 		return pwd
 	}
-	folderSeparator := pt.props.GetString(FolderSeparatorIcon, defaultSeparator)
+	folderSeparator := pt.getFolderSeparator()
 	if folderSeparator == defaultSeparator {
 		return pwd
 	}
diff --git a/src/template/text.go b/src/template/text.go
index 03291209..65e09a85 100644
--- a/src/template/text.go
+++ b/src/template/text.go
@@ -43,6 +43,7 @@ func (t *Text) Render() (string, error) {
 	t.cleanTemplate()
 	tmpl, err := template.New("title").Funcs(funcMap()).Parse(t.Template)
 	if err != nil {
+		t.Env.Log(environment.Error, "Render", err.Error())
 		return "", errors.New(InvalidTemplate)
 	}
 	context := &Context{}
@@ -51,6 +52,7 @@ func (t *Text) Render() (string, error) {
 	defer buffer.Reset()
 	err = tmpl.Execute(buffer, context)
 	if err != nil {
+		t.Env.Log(environment.Error, "Render", err.Error())
 		return "", errors.New(IncorrectTemplate)
 	}
 	text := buffer.String()


Test patch:
diff --git a/src/segments/path_test.go b/src/segments/path_test.go
index 75128f54..e183f85f 100644
--- a/src/segments/path_test.go
+++ b/src/segments/path_test.go
@@ -9,6 +9,7 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	mock2 "github.com/stretchr/testify/mock"
 )
 
 func renderTemplate(env *mock.MockedEnvironment, segmentTemplate string, context interface{}) string {
@@ -24,6 +25,7 @@ func renderTemplate(env *mock.MockedEnvironment, segmentTemplate string, context
 			Env: make(map[string]string),
 		})
 	}
+	env.On("Log", mock2.Anything, mock2.Anything, mock2.Anything)
 	tmpl := &template.Text{
 		Template: segmentTemplate,
 		Context:  context,
@@ -661,3 +663,41 @@ func TestGetPwd(t *testing.T) {
 		assert.Equal(t, tc.Expected, got)
 	}
 }
+
+func TestGetFolderSeparator(t *testing.T) {
+	cases := []struct {
+		Case                    string
+		FolderSeparatorIcon     string
+		FolderSeparatorTemplate string
+		Expected                string
+	}{
+		{Case: "default", Expected: "/"},
+		{Case: "icon - no template", FolderSeparatorIcon: "\ue5fe", Expected: "\ue5fe"},
+		{Case: "template", FolderSeparatorTemplate: "{{ if eq .Shell \"bash\" }}\\{{ end }}", Expected: "\\"},
+		{Case: "template empty", FolderSeparatorTemplate: "{{ if eq .Shell \"pwsh\" }}\\{{ end }}", Expected: ""},
+		{Case: "invalid template", FolderSeparatorTemplate: "{{ if eq .Shell \"pwsh\" }}", Expected: ""},
+	}
+
+	for _, tc := range cases {
+		env := new(mock.MockedEnvironment)
+		env.On("PathSeparator").Return("/")
+		env.On("Log", mock2.Anything, mock2.Anything, mock2.Anything)
+		path := &Path{
+			env: env,
+		}
+		props := properties.Map{}
+		if len(tc.FolderSeparatorTemplate) > 0 {
+			props[FolderSeparatorTemplate] = tc.FolderSeparatorTemplate
+		}
+		if len(tc.FolderSeparatorIcon) > 0 {
+			props[FolderSeparatorIcon] = tc.FolderSeparatorIcon
+		}
+		env.On("TemplateCache").Return(&environment.TemplateCache{
+			Env:   make(map[string]string),
+			Shell: "bash",
+		})
+		path.props = props
+		got := path.getFolderSeparator()
+		assert.Equal(t, tc.Expected, got)
+	}
+}
diff --git a/src/template/link_test.go b/src/template/link_test.go
index 6551bbb5..c5bcbb68 100644
--- a/src/template/link_test.go
+++ b/src/template/link_test.go
@@ -6,6 +6,7 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	mock2 "github.com/stretchr/testify/mock"
 )
 
 func TestUrl(t *testing.T) {
@@ -23,6 +24,7 @@ func TestUrl(t *testing.T) {
 	env.On("TemplateCache").Return(&environment.TemplateCache{
 		Env: make(map[string]string),
 	})
+	env.On("Log", mock2.Anything, mock2.Anything, mock2.Anything)
 	for _, tc := range cases {
 		tmpl := &Text{
 			Template: tc.Template,
diff --git a/src/template/round_test.go b/src/template/round_test.go
index f316eb10..7823cd59 100644
--- a/src/template/round_test.go
+++ b/src/template/round_test.go
@@ -6,6 +6,7 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	mock2 "github.com/stretchr/testify/mock"
 )
 
 func TestRoundSeconds(t *testing.T) {
@@ -30,6 +31,7 @@ func TestRoundSeconds(t *testing.T) {
 	env.On("TemplateCache").Return(&environment.TemplateCache{
 		Env: make(map[string]string),
 	})
+	env.On("Log", mock2.Anything, mock2.Anything, mock2.Anything)
 	for _, tc := range cases {
 		tmpl := &Text{
 			Template: tc.Template,
diff --git a/src/template/text_test.go b/src/template/text_test.go
index b3066486..c5dd1744 100644
--- a/src/template/text_test.go
+++ b/src/template/text_test.go
@@ -6,6 +6,7 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
+	mock2 "github.com/stretchr/testify/mock"
 )
 
 func TestRenderTemplate(t *testing.T) {
@@ -74,6 +75,7 @@ func TestRenderTemplate(t *testing.T) {
 	env.On("TemplateCache").Return(&environment.TemplateCache{
 		Env: make(map[string]string),
 	})
+	env.On("Log", mock2.Anything, mock2.Anything, mock2.Anything)
 	for _, tc := range cases {
 		tmpl := &Text{
 			Template: tc.Template,
@@ -128,6 +130,7 @@ func TestRenderTemplateEnvVar(t *testing.T) {
 		env.On("TemplateCache").Return(&environment.TemplateCache{
 			Env: tc.Env,
 		})
+		env.On("Log", mock2.Anything, mock2.Anything, mock2.Anything)
 		tmpl := &Text{
 			Template: tc.Template,
 			Context:  tc.Context,