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  }