cuelang.org/go@v0.13.0/cue/ast/importpath.go (about) 1 package ast 2 3 import "strings" 4 5 // ParseImportPath returns the various components of an import path. 6 // It does not check the result for validity. 7 func ParseImportPath(p string) ImportPath { 8 var parts ImportPath 9 pathWithoutQualifier := p 10 if i := strings.LastIndexAny(p, "/:"); i >= 0 && p[i] == ':' { 11 pathWithoutQualifier = p[:i] 12 parts.Qualifier = p[i+1:] 13 parts.ExplicitQualifier = true 14 } 15 parts.Path = pathWithoutQualifier 16 if path, version, ok := SplitPackageVersion(pathWithoutQualifier); ok { 17 parts.Version = version 18 parts.Path = path 19 } 20 if !parts.ExplicitQualifier { 21 if i := strings.LastIndex(parts.Path, "/"); i >= 0 { 22 parts.Qualifier = parts.Path[i+1:] 23 } else { 24 parts.Qualifier = parts.Path 25 } 26 if !IsValidIdent(parts.Qualifier) || strings.HasPrefix(parts.Qualifier, "#") || parts.Qualifier == "_" { 27 parts.Qualifier = "" 28 } 29 } 30 return parts 31 } 32 33 // ImportPath holds the various components of an import path. 34 type ImportPath struct { 35 // Path holds the base package/directory path, similar 36 // to that returned by [Version.BasePath]. 37 Path string 38 39 // Version holds the version of the import 40 // or empty if not present. Note: in general this 41 // will contain a major version only, but there's no 42 // guarantee of that. 43 Version string 44 45 // Qualifier holds the package qualifier within the path. 46 // This will be derived from the last component of Path 47 // if it wasn't explicitly present in the import path. 48 // This is not guaranteed to be a valid CUE identifier. 49 Qualifier string 50 51 // ExplicitQualifier holds whether the qualifier will 52 // always be added regardless of whether it matches 53 // the final path element. 54 ExplicitQualifier bool 55 } 56 57 // Canonical returns the canonical form of the import path. 58 // Specifically, it will only include the package qualifier 59 // if it's different from the last component of parts.Path. 60 func (parts ImportPath) Canonical() ImportPath { 61 if i := strings.LastIndex(parts.Path, "/"); i >= 0 && parts.Path[i+1:] == parts.Qualifier { 62 parts.Qualifier = "" 63 parts.ExplicitQualifier = false 64 } 65 return parts 66 } 67 68 // Unqualified returns the import path without any package qualifier. 69 func (parts ImportPath) Unqualified() ImportPath { 70 parts.Qualifier = "" 71 parts.ExplicitQualifier = false 72 return parts 73 } 74 75 func (parts ImportPath) String() string { 76 needQualifier := parts.ExplicitQualifier 77 if !needQualifier && parts.Qualifier != "" { 78 _, last, _ := cutLast(parts.Path, "/") 79 if last != "" && last != parts.Qualifier { 80 needQualifier = true 81 } 82 } 83 if parts.Version == "" && !needQualifier { 84 // Fast path. 85 return parts.Path 86 } 87 var buf strings.Builder 88 buf.WriteString(parts.Path) 89 if parts.Version != "" { 90 buf.WriteByte('@') 91 buf.WriteString(parts.Version) 92 } 93 if needQualifier { 94 buf.WriteByte(':') 95 buf.WriteString(parts.Qualifier) 96 } 97 return buf.String() 98 } 99 100 // SplitPackageVersion returns a prefix and version suffix such that 101 // prefix+"@"+version == path. 102 // 103 // SplitPackageVersion returns (path, "", false) when there is no `@` 104 // character splitting the path or if the version is empty. 105 // 106 // It does not check that the version is valid in any way other than 107 // checking that it is not empty. 108 // 109 // For example: 110 // 111 // SplitPackageVersion("foo.com/bar@v0.1") returns ("foo.com/bar", "v0.1", true). 112 // SplitPackageVersion("foo.com/bar@badvers") returns ("foo.com/bar", "badvers", true). 113 // SplitPackageVersion("foo.com/bar") returns ("foo.com/bar", "", false). 114 // SplitPackageVersion("foo.com/bar@") returns ("foo.com/bar@", "", false). 115 func SplitPackageVersion(path string) (prefix, version string, ok bool) { 116 prefix, vers, ok := strings.Cut(path, "@") 117 if vers == "" { 118 ok = false 119 } 120 return prefix, vers, ok 121 } 122 123 func cutLast(s, sep string) (before, after string, found bool) { 124 if i := strings.LastIndex(s, sep); i >= 0 { 125 return s[:i], s[i+len(sep):], true 126 } 127 return "", s, false 128 }