Skip to content

Commit

Permalink
feat(regex): expose findP
Browse files Browse the repository at this point in the history
  • Loading branch information
JanDeDobbeleer committed Feb 18, 2025
1 parent a81b233 commit fa50405
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 16 deletions.
69 changes: 58 additions & 11 deletions src/regex/regex.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package regex
import (
"regexp"
"sync"

"github.com/jandedobbeleer/oh-my-posh/src/log"
)

var (
Expand All @@ -14,50 +16,67 @@ const (
LINK = `(?P<STR>\x1b]8;;(.+)\x1b\\(?P<TEXT>.+)\x1b]8;;\x1b\\)`
)

func GetCompiledRegex(pattern string) *regexp.Regexp {
func GetCompiledRegex(pattern string) (*regexp.Regexp, error) {
// try in cache first
regexCacheLock.RLock()
re := regexCache[pattern]
regexCacheLock.RUnlock()
if re != nil {
return re
return re, nil
}

// should we panic or return the error?
re = regexp.MustCompile(pattern)
re, err := regexp.Compile(pattern)
if err != nil {
log.Error(err)
return nil, err
}

// lock for concurrent access and save the compiled expression in cache
regexCacheLock.Lock()
regexCache[pattern] = re
regexCacheLock.Unlock()

return re
return re, nil
}

func FindNamedRegexMatch(pattern, text string) map[string]string {
// error ignored because mustCompile will cause a panic
re := GetCompiledRegex(pattern)
match := re.FindStringSubmatch(text)
result := make(map[string]string)

re, err := GetCompiledRegex(pattern)
if err != nil {
return result
}

match := re.FindStringSubmatch(text)
if len(match) == 0 {
return result
}

for i, name := range re.SubexpNames() {
if i == 0 {
continue
}
result[name] = match[i]
}

return result
}

func FindAllNamedRegexMatch(pattern, text string) []map[string]string {
re := GetCompiledRegex(pattern)
match := re.FindAllStringSubmatch(text, -1)
var results []map[string]string

re, err := GetCompiledRegex(pattern)
if err != nil {
return results
}

match := re.FindAllStringSubmatch(text, -1)

if len(match) == 0 {
return results
}

for _, set := range match {
result := make(map[string]string)
for i, name := range re.SubexpNames() {
Expand All @@ -69,15 +88,43 @@ func FindAllNamedRegexMatch(pattern, text string) []map[string]string {
}
results = append(results, result)
}

return results
}

func ReplaceAllString(pattern, text, replaceText string) string {
re := GetCompiledRegex(pattern)
re, err := GetCompiledRegex(pattern)
if err != nil {
return text
}

return re.ReplaceAllString(text, replaceText)
}

func MatchString(pattern, text string) bool {
re := GetCompiledRegex(pattern)
re, err := GetCompiledRegex(pattern)
if err != nil {
return false
}

return re.MatchString(text)
}

func FindStringMatch(pattern, text string, index int) string {
re, err := GetCompiledRegex(pattern)
if err != nil {
return text
}

matches := re.FindStringSubmatch(text)
if len(matches) <= index {
return text
}

match := matches[index]
if len(match) == 0 {
return text
}

return match
}
72 changes: 72 additions & 0 deletions src/regex/regex_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package regex

import (
"testing"

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

func TestFindStringMatch(t *testing.T) {
cases := []struct {

Check failure on line 10 in src/regex/regex_test.go

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest)

struct with 64 pointer bytes could be 56
Case string
Pattern string
Text string
Index int
Expected string
}{
{
Case: "Full match at index 0",
Pattern: `\w+`,
Text: "hello",
Index: 0,
Expected: "hello",
},
{
Case: "Capture group at index 1",
Pattern: `hello (\w+)`,
Text: "hello world",
Index: 1,
Expected: "world",
},
{
Case: "No matches returns original text",
Pattern: `\d+`,
Text: "hello",
Index: 0,
Expected: "hello",
},
{
Case: "Invalid pattern returns original text",
Pattern: `[invalid`,
Text: "hello",
Index: 0,
Expected: "hello",
},
{
Case: "Empty text returns empty string",
Pattern: `\w+`,
Text: "",
Index: 0,
Expected: "",
},
{
Case: "Index out of bounds returns original text",
Pattern: `(\w+)`,
Text: "hello",
Index: 2,
Expected: "hello",
},
{
Case: "Multiple capture groups",
Pattern: `(\w+)\s(\w+)`,
Text: "hello world",
Index: 2,
Expected: "world",
},
}

for _, tc := range cases {
got := FindStringMatch(tc.Pattern, tc.Text, tc.Index)
assert.Equal(t, tc.Expected, got, tc.Case)
}
}
6 changes: 4 additions & 2 deletions src/runtime/path/clean.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ func Clean(input string) string {
}

// Always use an uppercase drive letter on Windows.
driveLetter := regex.GetCompiledRegex(`^[a-z]:`)
cleaned = driveLetter.ReplaceAllStringFunc(cleaned, strings.ToUpper)
driveLetter, err := regex.GetCompiledRegex(`^[a-z]:`)
if err == nil {
cleaned = driveLetter.ReplaceAllStringFunc(cleaned, strings.ToUpper)
}
}

sb := new(strings.Builder)
Expand Down
9 changes: 7 additions & 2 deletions src/runtime/terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,14 @@ func (term *Terminal) setPwd() {
if term.GOOS() != WINDOWS {
return pwd
}

// on Windows, and being case sensitive and not consistent and all, this gives silly issues
driveLetter := regex.GetCompiledRegex(`^[a-z]:`)
return driveLetter.ReplaceAllStringFunc(pwd, strings.ToUpper)
driveLetter, err := regex.GetCompiledRegex(`^[a-z]:`)
if err == nil {
return driveLetter.ReplaceAllStringFunc(pwd, strings.ToUpper)
}

return pwd
}

if term.CmdFlags != nil && term.CmdFlags.PWD != "" {
Expand Down
2 changes: 1 addition & 1 deletion src/segments/scm.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func (s *scm) formatBranch(branch string) string {
}

branchTemplate := s.props.GetString(BranchTemplate, "")
if branchTemplate == "" {
if len(branchTemplate) == 0 {
return branch
}

Expand Down
1 change: 1 addition & 0 deletions src/template/func_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func funcMap() template.FuncMap {
"path": filePath,
"glob": glob,
"matchP": matchP,
"findP": findP,
"replaceP": replaceP,
"gt": gt,
"lt": lt,
Expand Down
4 changes: 4 additions & 0 deletions src/template/regex.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ func matchP(pattern, text string) bool {
func replaceP(pattern, text, replaceText string) string {
return regex.ReplaceAllString(pattern, text, replaceText)
}

func findP(pattern, text string, index int) string {
return regex.FindStringMatch(pattern, text, index)
}

0 comments on commit fa50405

Please sign in to comment.