github.com/wolfd/bazel-gazelle@v0.14.0/internal/label/label.go (about) 1 /* Copyright 2016 The Bazel Authors. All rights reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package label 17 18 import ( 19 "fmt" 20 "log" 21 "path" 22 "regexp" 23 "strings" 24 25 "github.com/bazelbuild/bazel-gazelle/internal/pathtools" 26 ) 27 28 // A Label represents a label of a build target in Bazel. 29 type Label struct { 30 Repo, Pkg, Name string 31 Relative bool 32 } 33 34 func New(repo, pkg, name string) Label { 35 return Label{Repo: repo, Pkg: pkg, Name: name} 36 } 37 38 // NoLabel is the nil value of Label. It is not a valid label and may be 39 // returned when an error occurs. 40 var NoLabel = Label{} 41 42 var ( 43 labelRepoRegexp = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]*$`) 44 labelPkgRegexp = regexp.MustCompile(`^[A-Za-z0-9/._-]*$`) 45 labelNameRegexp = regexp.MustCompile(`^[A-Za-z0-9_/.+=,@~-]*$`) 46 ) 47 48 // Parse reads a label from a string. 49 // See https://docs.bazel.build/versions/master/build-ref.html#lexi. 50 func Parse(s string) (Label, error) { 51 origStr := s 52 53 relative := true 54 var repo string 55 if strings.HasPrefix(s, "@") { 56 relative = false 57 endRepo := strings.Index(s, "//") 58 if endRepo < 0 { 59 return NoLabel, fmt.Errorf("label parse error: repository does not end with '//': %q", origStr) 60 } 61 repo = s[len("@"):endRepo] 62 if !labelRepoRegexp.MatchString(repo) { 63 return NoLabel, fmt.Errorf("label parse error: repository has invalid characters: %q", origStr) 64 } 65 s = s[endRepo:] 66 } 67 68 var pkg string 69 if strings.HasPrefix(s, "//") { 70 relative = false 71 endPkg := strings.Index(s, ":") 72 if endPkg < 0 { 73 pkg = s[len("//"):] 74 s = "" 75 } else { 76 pkg = s[len("//"):endPkg] 77 s = s[endPkg:] 78 } 79 if !labelPkgRegexp.MatchString(pkg) { 80 return NoLabel, fmt.Errorf("label parse error: package has invalid characters: %q", origStr) 81 } 82 } 83 84 if s == ":" { 85 return NoLabel, fmt.Errorf("label parse error: empty name: %q", origStr) 86 } 87 name := strings.TrimPrefix(s, ":") 88 if !labelNameRegexp.MatchString(name) { 89 return NoLabel, fmt.Errorf("label parse error: name has invalid characters: %q", origStr) 90 } 91 92 if pkg == "" && name == "" { 93 return NoLabel, fmt.Errorf("label parse error: empty package and name: %q", origStr) 94 } 95 if name == "" { 96 name = path.Base(pkg) 97 } 98 99 return Label{ 100 Repo: repo, 101 Pkg: pkg, 102 Name: name, 103 Relative: relative, 104 }, nil 105 } 106 107 func (l Label) String() string { 108 if l.Relative { 109 return fmt.Sprintf(":%s", l.Name) 110 } 111 112 var repo string 113 if l.Repo != "" { 114 repo = fmt.Sprintf("@%s", l.Repo) 115 } 116 117 if path.Base(l.Pkg) == l.Name { 118 return fmt.Sprintf("%s//%s", repo, l.Pkg) 119 } 120 return fmt.Sprintf("%s//%s:%s", repo, l.Pkg, l.Name) 121 } 122 123 func (l Label) Abs(repo, pkg string) Label { 124 if !l.Relative { 125 return l 126 } 127 return Label{Repo: repo, Pkg: pkg, Name: l.Name} 128 } 129 130 func (l Label) Rel(repo, pkg string) Label { 131 if l.Relative || l.Repo != repo { 132 return l 133 } 134 if l.Pkg == pkg { 135 return Label{Name: l.Name, Relative: true} 136 } 137 return Label{Pkg: l.Pkg, Name: l.Name} 138 } 139 140 func (l Label) Equal(other Label) bool { 141 return l.Repo == other.Repo && 142 l.Pkg == other.Pkg && 143 l.Name == other.Name && 144 l.Relative == other.Relative 145 } 146 147 // Contains returns whether other is contained by the package of l or a 148 // sub-package. Neither label may be relative. 149 func (l Label) Contains(other Label) bool { 150 if l.Relative { 151 log.Panicf("l must not be relative: %s", l) 152 } 153 if other.Relative { 154 log.Panicf("other must not be relative: %s", other) 155 } 156 result := l.Repo == other.Repo && pathtools.HasPrefix(other.Pkg, l.Pkg) 157 return result 158 } 159 160 // ImportPathToBazelRepoName converts a Go import path into a bazel repo name 161 // following the guidelines in http://bazel.io/docs/be/functions.html#workspace 162 func ImportPathToBazelRepoName(importpath string) string { 163 importpath = strings.ToLower(importpath) 164 components := strings.Split(importpath, "/") 165 labels := strings.Split(components[0], ".") 166 var reversed []string 167 for i := range labels { 168 l := labels[len(labels)-i-1] 169 reversed = append(reversed, l) 170 } 171 repo := strings.Join(append(reversed, components[1:]...), "_") 172 return strings.NewReplacer("-", "_", ".", "_").Replace(repo) 173 }