Skip to content

Commit

Permalink
feat: flexible matching for include/exclude folders
Browse files Browse the repository at this point in the history
  • Loading branch information
asherber authored and JanDeDobbeleer committed Sep 21, 2021
1 parent b251565 commit 7e48657
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 13 deletions.
24 changes: 13 additions & 11 deletions docs/docs/configuration.md
Expand Up @@ -293,8 +293,9 @@ will not be rendered when in one of the excluded locations.
]
```

You can also specify a [regular expression][regex] to create folder wildcards.
In the sample below, any folders inside the `/Users/posh/Projects` path will be matched.
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
name. The following will match `/Users/posh/Projects/Foo` but not `/home/Users/posh/Projects/Foo`.

```json
"include_folders": [
Expand All @@ -313,16 +314,17 @@ You can also combine these properties:
]
```

Note for Windows users: Windows directory separators should be specified as 4 backslashes.
##### Notes

```json
"include_folders": [
"C:\\\\Projects.*"
],
"exclude_folders": [
"C:\\\\Projects\\\\secret-project.*"
]
```
- 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 `\\\\`.
- 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.

This means that for user Bill, who has a user account `Bill` on Windows and `bill` on Linux, `~/Foo` might match
`C:\Users\Bill\Foo` or `C:\Users\Bill\foo` on Windows but only `/home/bill/Foo` on Linux.

### Colors

Expand Down
16 changes: 14 additions & 2 deletions src/segment.go
Expand Up @@ -3,6 +3,7 @@ package main
import (
"errors"
"fmt"
"strings"
"time"
)

Expand Down Expand Up @@ -176,9 +177,20 @@ func (segment *Segment) cwdExcluded(cwd string) bool {
}

func (segment *Segment) cwdMatchesOneOf(cwd string, regexes []string) bool {
normalizedCwd := strings.ReplaceAll(cwd, "\\", "/")
normalizedHomeDir := strings.ReplaceAll(segment.env.homeDir(), "\\", "/")

for _, element := range regexes {
pattern := fmt.Sprintf("^%s$", element)
matched := matchString(pattern, cwd)
normalizedElement := strings.ReplaceAll(element, "\\\\", "/")
if strings.HasPrefix(normalizedElement, "~") {
normalizedElement = normalizedHomeDir + normalizedElement[1:]
}
pattern := fmt.Sprintf("^%s$", normalizedElement)
goos := segment.env.getRuntimeGOOS()
if goos == windowsPlatform || goos == darwinPlatform {
pattern = "(?i)" + pattern
}
matched := matchString(pattern, normalizedCwd)
if matched {
return true
}
Expand Down
48 changes: 48 additions & 0 deletions src/segment_test.go
Expand Up @@ -81,22 +81,30 @@ func TestShouldIncludeFolder(t *testing.T) {
{Case: "Include Mismatch / Exclude Mismatch", IncludeFolders: []string{"zProjects.*"}, ExcludeFolders: []string{"Projects/nope"}, Expected: false},
}
for _, tc := range cases {
env := new(MockedEnvironment)
env.On("getRuntimeGOOS", nil).Return(linuxPlatform)
env.On("homeDir", nil).Return("")
segment := &Segment{
Properties: map[Property]interface{}{
IncludeFolders: tc.IncludeFolders,
ExcludeFolders: tc.ExcludeFolders,
},
env: env,
}
got := segment.shouldIncludeFolder(cwd)
assert.Equal(t, tc.Expected, got, tc.Case)
}
}

func TestShouldIncludeFolderRegexInverted(t *testing.T) {
env := new(MockedEnvironment)
env.On("getRuntimeGOOS", nil).Return(linuxPlatform)
env.On("homeDir", nil).Return("")
segment := &Segment{
Properties: map[Property]interface{}{
ExcludeFolders: []string{"(?!Projects[\\/]).*"},
},
env: env,
}
// detect panic(thrown by MustCompile)
defer func() {
Expand All @@ -109,10 +117,14 @@ func TestShouldIncludeFolderRegexInverted(t *testing.T) {
}

func TestShouldIncludeFolderRegexInvertedNonEscaped(t *testing.T) {
env := new(MockedEnvironment)
env.On("getRuntimeGOOS", nil).Return(linuxPlatform)
env.On("homeDir", nil).Return("")
segment := &Segment{
Properties: map[Property]interface{}{
ExcludeFolders: []string{"(?!Projects/).*"},
},
env: env,
}
// detect panic(thrown by MustCompile)
defer func() {
Expand Down Expand Up @@ -196,3 +208,39 @@ func TestGetColors(t *testing.T) {
assert.Equal(t, tc.ExpectedColor, color, tc.Case)
}
}

func TestCwdMatchesOneOf(t *testing.T) {
cases := []struct {
GOOS string
HomeDir string
Cwd string
Pattern string
Expected bool
}{
{GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill", Pattern: "/home/bill", Expected: true},
{GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill/foo", Pattern: "~/foo", Expected: true},
{GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill/foo", Pattern: "~/Foo", Expected: false},
{GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill/foo", Pattern: "~\\\\foo", Expected: true},
{GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill/foo/bar", Pattern: "~/fo.*", Expected: true},
{GOOS: linuxPlatform, HomeDir: "/home/bill", Cwd: "/home/bill/foo", Pattern: "~/fo\\w", Expected: true},
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill", Pattern: "C:\\\\Users\\\\Bill", Expected: true},
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill", Pattern: "C:/Users/Bill", Expected: true},
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill", Pattern: "c:/users/bill", Expected: true},
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill", Pattern: "~", Expected: true},
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill\\Foo", Pattern: "~/Foo", Expected: true},
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill\\Foo", Pattern: "~/foo", Expected: true},
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill\\Foo\\Bar", Pattern: "~/fo.*", Expected: true},
{GOOS: windowsPlatform, HomeDir: "C:\\Users\\Bill", Cwd: "C:\\Users\\Bill\\Foo", Pattern: "~/fo\\w", Expected: true},
}

for _, tc := range cases {
env := new(MockedEnvironment)
env.On("getRuntimeGOOS", nil).Return(tc.GOOS)
env.On("homeDir", nil).Return(tc.HomeDir)
segment := &Segment{
env: env,
}
got := segment.cwdMatchesOneOf(tc.Cwd, []string{tc.Pattern})
assert.Equal(t, tc.Expected, got)
}
}

0 comments on commit 7e48657

Please sign in to comment.