fix(path): remove duplicate logic

resolves #2923
This commit is contained in:
Jan De Dobbeleer 2022-10-12 11:48:52 +02:00 committed by Jan De Dobbeleer
parent 21922aa678
commit ac0ffec43a
3 changed files with 623 additions and 586 deletions

View file

@ -344,17 +344,15 @@ func (env *ShellEnvironment) Pwd() string {
return env.cwd return env.cwd
} }
correctPath := func(pwd string) string { correctPath := func(pwd string) string {
// on Windows, and being case sensitive and not consistent and all, this gives silly issues if env.GOOS() != WINDOWS {
if env.GOOS() == WINDOWS { return pwd
driveLetter := regex.GetCompiledRegex(`^[a-z]:`)
return driveLetter.ReplaceAllStringFunc(pwd, strings.ToUpper)
} }
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)
} }
if env.CmdFlags != nil && env.CmdFlags.PWD != "" { if env.CmdFlags != nil && env.CmdFlags.PWD != "" {
// ensure a clean path env.cwd = correctPath(env.CmdFlags.PWD)
root, path := ParsePath(env, correctPath(env.CmdFlags.PWD))
env.cwd = root + path
return env.cwd return env.cwd
} }
dir, err := os.Getwd() dir, err := os.Getwd()
@ -846,47 +844,6 @@ func Base(env Environment, path string) string {
return path return path
} }
// ParsePath parses an input path and returns a clean root and a clean path.
func ParsePath(env Environment, inputPath string) (root, path string) {
if len(inputPath) == 0 {
return
}
separator := env.PathSeparator()
clean := func(path string) string {
matches := regex.FindAllNamedRegexMatch(fmt.Sprintf(`(?P<element>[^\%s]+)`, separator), path)
n := len(matches) - 1
s := new(strings.Builder)
for i, m := range matches {
s.WriteString(m["element"])
if i != n {
s.WriteString(separator)
}
}
return s.String()
}
if env.GOOS() == WINDOWS {
inputPath = strings.ReplaceAll(inputPath, "/", `\`)
// for a UNC path, extract \\hostname\sharename as the root
matches := regex.FindNamedRegexMatch(`^\\\\(?P<hostname>[^\\]+)\\+(?P<sharename>[^\\]+)\\*(?P<path>[\s\S]*)$`, inputPath)
if len(matches) > 0 {
root = `\\` + matches["hostname"] + `\` + matches["sharename"] + `\`
path = clean(matches["path"])
return
}
}
s := strings.SplitAfterN(inputPath, separator, 2)
root = s[0]
if !strings.HasSuffix(root, separator) {
// a root should end with a separator
root += separator
}
if len(s) == 2 {
path = clean(s[1])
}
return root, path
}
func ReplaceHomeDirPrefixWithTilde(env Environment, path string) string { func ReplaceHomeDirPrefixWithTilde(env Environment, path string) string {
home := env.Home() home := env.Home()
// match Home directory exactly // match Home directory exactly

View file

@ -15,7 +15,10 @@ type Path struct {
props properties.Properties props properties.Properties
env environment.Environment env environment.Environment
pwd string root string
relative string
pwd string
Path string Path string
StackCount int StackCount int
Location string Location string
@ -70,7 +73,11 @@ func (pt *Path) Template() string {
} }
func (pt *Path) Enabled() bool { func (pt *Path) Enabled() bool {
pt.setPath() pt.setPaths()
if len(pt.pwd) == 0 {
return false
}
pt.setStyle()
if pt.env.IsWsl() { if pt.env.IsWsl() {
pt.Location, _ = pt.env.RunCommand("wslpath", "-m", pt.pwd) pt.Location, _ = pt.env.RunCommand("wslpath", "-m", pt.pwd)
} else { } else {
@ -81,22 +88,35 @@ func (pt *Path) Enabled() bool {
return true return true
} }
func (pt *Path) setPaths() {
pt.pwd = pt.env.Pwd()
if (pt.env.Shell() == shell.PWSH || pt.env.Shell() == shell.PWSH5) && len(pt.env.Flags().PSWD) != 0 {
pt.pwd = pt.env.Flags().PSWD
}
if len(pt.pwd) == 0 {
return
}
// ensure a clean path
pt.root, pt.relative = pt.replaceMappedLocations()
pathSeparator := pt.env.PathSeparator()
if !strings.HasSuffix(pt.root, pathSeparator) && len(pt.relative) > 0 {
pt.pwd = pt.root + pathSeparator + pt.relative
return
}
pt.pwd = pt.root + pt.relative
}
func (pt *Path) Parent() string { func (pt *Path) Parent() string {
pwd := pt.getPwd() if len(pt.pwd) == 0 {
if len(pwd) == 0 {
return "" return ""
} }
root, path := environment.ParsePath(pt.env, pwd) if len(pt.relative) == 0 {
if len(path) == 0 {
// a root path has no parent // a root path has no parent
return "" return ""
} }
base := environment.Base(pt.env, path) base := environment.Base(pt.env, pt.pwd)
path = pt.replaceFolderSeparators(path[:len(path)-len(base)]) path := pt.replaceFolderSeparators(pt.pwd[:len(pt.pwd)-len(base)])
if root != pt.env.PathSeparator() { return path
root = root[:len(root)-1] + pt.getFolderSeparator()
}
return root + path
} }
func (pt *Path) Init(props properties.Properties, env environment.Environment) { func (pt *Path) Init(props properties.Properties, env environment.Environment) {
@ -104,38 +124,33 @@ func (pt *Path) Init(props properties.Properties, env environment.Environment) {
pt.env = env pt.env = env
} }
func (pt *Path) setPath() { func (pt *Path) setStyle() {
pwd := pt.getPwd() if len(pt.relative) == 0 {
if len(pwd) == 0 { pt.Path = pt.root
return
}
root, path := environment.ParsePath(pt.env, pwd)
if len(path) == 0 {
pt.Path = pt.formatRoot(root)
return return
} }
switch style := pt.props.GetString(properties.Style, Agnoster); style { switch style := pt.props.GetString(properties.Style, Agnoster); style {
case Agnoster: case Agnoster:
pt.Path = pt.getAgnosterPath(root, path) pt.Path = pt.getAgnosterPath()
case AgnosterFull: case AgnosterFull:
pt.Path = pt.getAgnosterFullPath(root, path) pt.Path = pt.getAgnosterFullPath()
case AgnosterShort: case AgnosterShort:
pt.Path = pt.getAgnosterShortPath(root, path) pt.Path = pt.getAgnosterShortPath()
case Mixed: case Mixed:
pt.Path = pt.getMixedPath(root, path) pt.Path = pt.getMixedPath()
case Letter: case Letter:
pt.Path = pt.getLetterPath(root, path) pt.Path = pt.getLetterPath()
case Unique: case Unique:
pt.Path = pt.getUniqueLettersPath(root, path) pt.Path = pt.getUniqueLettersPath()
case AgnosterLeft: case AgnosterLeft:
pt.Path = pt.getAgnosterLeftPath(root, path) pt.Path = pt.getAgnosterLeftPath()
case Short: case Short:
// "short" is a duplicate of "full", just here for backwards compatibility // "short" is a duplicate of "full", just here for backwards compatibility
fallthrough fallthrough
case Full: case Full:
pt.Path = pt.getFullPath(root, path) pt.Path = pt.getFullPath()
case Folder: case Folder:
pt.Path = pt.getFolderPath(path) pt.Path = pt.getFolderPath()
default: default:
pt.Path = fmt.Sprintf("Path style: %s is not available", style) pt.Path = fmt.Sprintf("Path style: %s is not available", style)
} }
@ -166,14 +181,14 @@ func (pt *Path) getFolderSeparator() string {
return text return text
} }
func (pt *Path) getMixedPath(root, path string) string { func (pt *Path) getMixedPath() string {
var buffer strings.Builder var buffer strings.Builder
threshold := int(pt.props.GetFloat64(MixedThreshold, 4)) threshold := int(pt.props.GetFloat64(MixedThreshold, 4))
folderIcon := pt.props.GetString(FolderIcon, "..") folderIcon := pt.props.GetString(FolderIcon, "..")
separator := pt.getFolderSeparator() separator := pt.getFolderSeparator()
elements := strings.Split(path, pt.env.PathSeparator()) elements := strings.Split(pt.relative, pt.env.PathSeparator())
if root != pt.env.PathSeparator() { if pt.root != pt.env.PathSeparator() {
elements = append([]string{root[:len(root)-1]}, elements...) elements = append([]string{pt.root}, elements...)
} }
n := len(elements) n := len(elements)
buffer.WriteString(elements[0]) buffer.WriteString(elements[0])
@ -187,13 +202,24 @@ func (pt *Path) getMixedPath(root, path string) string {
return buffer.String() return buffer.String()
} }
func (pt *Path) getAgnosterPath(root, path string) string { func (pt *Path) pathDepth(pwd string) int {
splitted := strings.Split(pwd, pt.env.PathSeparator())
depth := 0
for _, part := range splitted {
if part != "" {
depth++
}
}
return depth
}
func (pt *Path) getAgnosterPath() string {
var buffer strings.Builder var buffer strings.Builder
folderIcon := pt.props.GetString(FolderIcon, "..") folderIcon := pt.props.GetString(FolderIcon, "..")
separator := pt.getFolderSeparator() separator := pt.getFolderSeparator()
elements := strings.Split(path, pt.env.PathSeparator()) elements := strings.Split(pt.relative, pt.env.PathSeparator())
if root != pt.env.PathSeparator() { if pt.root != pt.env.PathSeparator() {
elements = append([]string{root[:len(root)-1]}, elements...) elements = append([]string{pt.root}, elements...)
} }
n := len(elements) n := len(elements)
buffer.WriteString(elements[0]) buffer.WriteString(elements[0])
@ -206,13 +232,13 @@ func (pt *Path) getAgnosterPath(root, path string) string {
return buffer.String() return buffer.String()
} }
func (pt *Path) getAgnosterLeftPath(root, path string) string { func (pt *Path) getAgnosterLeftPath() string {
var buffer strings.Builder var buffer strings.Builder
folderIcon := pt.props.GetString(FolderIcon, "..") folderIcon := pt.props.GetString(FolderIcon, "..")
separator := pt.getFolderSeparator() separator := pt.getFolderSeparator()
elements := strings.Split(path, pt.env.PathSeparator()) elements := strings.Split(pt.relative, pt.env.PathSeparator())
if root != pt.env.PathSeparator() { if pt.root != pt.env.PathSeparator() {
elements = append([]string{root[:len(root)-1]}, elements...) elements = append([]string{pt.root}, elements...)
} }
n := len(elements) n := len(elements)
buffer.WriteString(elements[0]) buffer.WriteString(elements[0])
@ -238,12 +264,12 @@ func (pt *Path) getRelevantLetter(folder string) string {
return letter return letter
} }
func (pt *Path) getLetterPath(root, path string) string { func (pt *Path) getLetterPath() string {
var buffer strings.Builder var buffer strings.Builder
separator := pt.getFolderSeparator() separator := pt.getFolderSeparator()
elements := strings.Split(path, pt.env.PathSeparator()) elements := strings.Split(pt.relative, pt.env.PathSeparator())
if root != pt.env.PathSeparator() { if pt.root != pt.env.PathSeparator() {
elements = append([]string{root[:len(root)-1]}, elements...) elements = append([]string{pt.root}, elements...)
} }
n := len(elements) n := len(elements)
for i := 0; i < n-1; i++ { for i := 0; i < n-1; i++ {
@ -257,12 +283,12 @@ func (pt *Path) getLetterPath(root, path string) string {
return buffer.String() return buffer.String()
} }
func (pt *Path) getUniqueLettersPath(root, path string) string { func (pt *Path) getUniqueLettersPath() string {
var buffer strings.Builder var buffer strings.Builder
separator := pt.getFolderSeparator() separator := pt.getFolderSeparator()
elements := strings.Split(path, pt.env.PathSeparator()) elements := strings.Split(pt.relative, pt.env.PathSeparator())
if root != pt.env.PathSeparator() { if pt.root != pt.env.PathSeparator() {
elements = append([]string{root[:len(root)-1]}, elements...) elements = append([]string{pt.root}, elements...)
} }
n := len(elements) n := len(elements)
letters := make(map[string]bool) letters := make(map[string]bool)
@ -285,112 +311,72 @@ func (pt *Path) getUniqueLettersPath(root, path string) string {
return buffer.String() return buffer.String()
} }
func (pt *Path) getAgnosterFullPath(root, path string) string { func (pt *Path) getAgnosterFullPath() string {
path := strings.Trim(pt.relative, pt.env.PathSeparator())
path = pt.replaceFolderSeparators(path) path = pt.replaceFolderSeparators(path)
if root == pt.env.PathSeparator() { if pt.root == pt.env.PathSeparator() {
return path return path
} }
root = root[:len(root)-1] + pt.getFolderSeparator() return pt.root + pt.getFolderSeparator() + path
return root + path
} }
func (pt *Path) getAgnosterShortPath(root, path string) string { func (pt *Path) getAgnosterShortPath() string {
elements := strings.Split(path, pt.env.PathSeparator()) pathDepth := pt.pathDepth(pt.relative)
if root != pt.env.PathSeparator() {
elements = append([]string{root[:len(root)-1]}, elements...)
}
depth := len(elements)
maxDepth := pt.props.GetInt(MaxDepth, 1) maxDepth := pt.props.GetInt(MaxDepth, 1)
if maxDepth < 1 { if maxDepth < 1 {
maxDepth = 1 maxDepth = 1
} }
hideRootLocation := pt.props.GetBool(HideRootLocation, false)
if !hideRootLocation {
maxDepth++
}
if depth <= maxDepth {
return pt.getAgnosterFullPath(root, path)
}
separator := pt.getFolderSeparator()
folderIcon := pt.props.GetString(FolderIcon, "..") folderIcon := pt.props.GetString(FolderIcon, "..")
var buffer strings.Builder hideRootLocation := pt.props.GetBool(HideRootLocation, false)
if !hideRootLocation { if pathDepth <= maxDepth {
buffer.WriteString(fmt.Sprintf("%s%s", elements[0], separator)) if hideRootLocation {
maxDepth-- pt.root = folderIcon
}
splitPos := depth - maxDepth
if splitPos != 1 {
buffer.WriteString(fmt.Sprintf("%s%s", folderIcon, separator))
}
for i := splitPos; i < depth; i++ {
buffer.WriteString(elements[i])
if i != depth-1 {
buffer.WriteString(separator)
} }
return pt.getAgnosterFullPath()
}
pathSeparator := pt.env.PathSeparator()
folderSeparator := pt.getFolderSeparator()
rel := strings.TrimPrefix(pt.relative, pathSeparator)
splitted := strings.Split(rel, pathSeparator)
splitPos := pathDepth - maxDepth
var buffer strings.Builder
// unix root, needs to be replaced with the folder we're in at root level
root := pt.root
room := pathDepth - maxDepth
if root == pathSeparator {
root = splitted[0]
room--
}
if hideRootLocation {
buffer.WriteString(folderIcon)
} else {
buffer.WriteString(root)
if room > 0 {
buffer.WriteString(folderSeparator)
buffer.WriteString(folderIcon)
}
}
for i := splitPos; i < pathDepth; i++ {
buffer.WriteString(fmt.Sprintf("%s%s", folderSeparator, splitted[i]))
} }
return buffer.String() return buffer.String()
} }
func (pt *Path) getFullPath(root, path string) string { func (pt *Path) getFullPath() string {
if root != pt.env.PathSeparator() { rel := pt.relative
root = root[:len(root)-1] + pt.getFolderSeparator() if pt.root != pt.env.PathSeparator() {
rel = pt.env.PathSeparator() + rel
} }
path = pt.replaceFolderSeparators(path) path := pt.replaceFolderSeparators(rel)
return root + path return pt.root + path
} }
func (pt *Path) getFolderPath(path string) string { func (pt *Path) getFolderPath() string {
return environment.Base(pt.env, path) pwd := environment.Base(pt.env, pt.pwd)
return pt.replaceFolderSeparators(pwd)
} }
func (pt *Path) setPwd() { func (pt *Path) replaceMappedLocations() (string, string) {
if len(pt.pwd) > 0 {
return
}
if pt.env.Shell() == shell.PWSH || pt.env.Shell() == shell.PWSH5 {
pt.pwd = pt.env.Flags().PSWD
}
if len(pt.pwd) == 0 {
pt.pwd = pt.env.Pwd()
return
}
// ensure a clean path
root, path := environment.ParsePath(pt.env, pt.pwd)
pt.pwd = root + path
}
func (pt *Path) getPwd() string {
pt.setPwd()
return pt.replaceMappedLocations(pt.pwd)
}
func (pt *Path) formatRoot(root string) string {
n := len(root)
// trim the trailing separator first
root = root[:n-1]
// only preserve the trailing separator for a Unix/Windows/PSDrive root
if len(root) == 0 || (strings.HasPrefix(pt.pwd, root) && strings.HasSuffix(root, ":")) {
return root + pt.env.PathSeparator()
}
return root
}
func (pt *Path) normalize(inputPath string) string {
normalized := inputPath
if strings.HasPrefix(normalized, "~") && (len(normalized) == 1 || environment.IsPathSeparator(pt.env, normalized[1])) {
normalized = pt.env.Home() + normalized[1:]
}
switch pt.env.GOOS() {
case environment.WINDOWS:
normalized = strings.ReplaceAll(normalized, "/", `\`)
fallthrough
case environment.DARWIN:
normalized = strings.ToLower(normalized)
}
return normalized
}
func (pt *Path) replaceMappedLocations(pwd string) string {
mappedLocations := map[string]string{} mappedLocations := map[string]string{}
// predefined mapped locations, can be disabled // predefined mapped locations, can be disabled
if pt.props.GetBool(MappedLocationsEnabled, true) { if pt.props.GetBool(MappedLocationsEnabled, true) {
@ -417,30 +403,117 @@ func (pt *Path) replaceMappedLocations(pwd string) string {
} }
sort.Sort(sort.Reverse(sort.StringSlice(keys))) sort.Sort(sort.Reverse(sort.StringSlice(keys)))
cleanPwdRoot, cleanPwdPath := environment.ParsePath(pt.env, pwd) root, relative := pt.parsePath(pt.pwd)
pwdRoot := pt.normalize(cleanPwdRoot) rootN := pt.normalize(root)
pwdPath := pt.normalize(cleanPwdPath) relativeN := pt.normalize(relative)
pathSeparator := pt.env.PathSeparator()
formatRoot := func(root string) string {
// trim the trailing separator first
root = strings.TrimSuffix(root, pathSeparator)
// only preserve the trailing separator for a Unix/Windows/PSDrive root
if len(root) == 0 || strings.HasSuffix(root, ":") {
return root + pathSeparator
}
return root
}
for _, key := range keys { for _, key := range keys {
keyRoot, keyPath := environment.ParsePath(pt.env, key) keyRoot, keyRelative := pt.parsePath(key)
if keyRoot != pwdRoot || !strings.HasPrefix(pwdPath, keyPath) { if keyRoot != rootN || !strings.HasPrefix(relativeN, keyRelative) {
continue continue
} }
value := mappedLocations[key] value := mappedLocations[key]
rem := cleanPwdPath[len(keyPath):] overflow := relative[len(keyRelative):]
if len(rem) == 0 { if len(overflow) == 0 {
// exactly match the full path // exactly match the full path
return value return formatRoot(value), ""
} }
if len(keyPath) == 0 { if len(keyRelative) == 0 {
// only match the root // only match the root
return value + pt.env.PathSeparator() + cleanPwdPath return formatRoot(value), strings.Trim(relative, pathSeparator)
} }
// match several prefix elements // match several prefix elements
if rem[0:1] == pt.env.PathSeparator() { if overflow[0:1] == pt.env.PathSeparator() {
return value + rem return formatRoot(value), strings.Trim(overflow, pathSeparator)
} }
} }
return cleanPwdRoot + cleanPwdPath return formatRoot(root), strings.Trim(relative, pathSeparator)
}
func (pt *Path) normalizePath(path string) string {
if pt.env.GOOS() != environment.WINDOWS {
return path
}
var clean []rune
for _, char := range path {
var lastChar rune
if len(clean) > 0 {
lastChar = clean[len(clean)-1:][0]
}
if char == '/' && lastChar != 60 { // 60 == <, this is done to ovoid replacing color codes
clean = append(clean, 92) // 92 == \
continue
}
clean = append(clean, char)
}
return string(clean)
}
// ParsePath parses an input path and returns a clean root and a clean path.
func (pt *Path) parsePath(inputPath string) (root, path string) {
if len(inputPath) == 0 {
return
}
separator := pt.env.PathSeparator()
clean := func(path string) string {
matches := regex.FindAllNamedRegexMatch(fmt.Sprintf(`(?P<element>[^\%s]+)`, separator), path)
n := len(matches) - 1
s := new(strings.Builder)
for i, m := range matches {
s.WriteString(m["element"])
if i != n {
s.WriteString(separator)
}
}
return s.String()
}
if pt.env.GOOS() == environment.WINDOWS {
inputPath = pt.normalizePath(inputPath)
// for a UNC path, extract \\hostname\sharename as the root
matches := regex.FindNamedRegexMatch(`^\\\\(?P<hostname>[^\\]+)\\+(?P<sharename>[^\\]+)\\*(?P<path>[\s\S]*)$`, inputPath)
if len(matches) > 0 {
root = `\\` + matches["hostname"] + `\` + matches["sharename"] + `\`
path = clean(matches["path"])
return
}
}
s := strings.SplitAfterN(inputPath, separator, 2)
root = s[0]
if !strings.HasSuffix(root, separator) {
// a root should end with a separator
root += separator
}
if len(s) == 2 {
path = clean(s[1])
}
return root, path
}
func (pt *Path) normalize(inputPath string) string {
normalized := inputPath
if strings.HasPrefix(normalized, "~") && (len(normalized) == 1 || environment.IsPathSeparator(pt.env, normalized[1])) {
normalized = pt.env.Home() + normalized[1:]
}
switch pt.env.GOOS() {
case environment.WINDOWS:
normalized = pt.normalizePath(normalized)
fallthrough
case environment.DARWIN:
normalized = strings.ToLower(normalized)
}
return normalized
} }
func (pt *Path) replaceFolderSeparators(pwd string) string { func (pt *Path) replaceFolderSeparators(pwd string) string {

File diff suppressed because it is too large Load diff