github.com/afking/bazel-gazelle@v0.0.0-20180301150245-c02bc0f529e8/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) Equal(other Label) bool { 131 return l.Repo == other.Repo && 132 l.Pkg == other.Pkg && 133 l.Name == other.Name && 134 l.Relative == other.Relative 135 } 136 137 // Contains returns whether other is contained by the package of l or a 138 // sub-package. Neither label may be relative. 139 func (l Label) Contains(other Label) bool { 140 if l.Relative { 141 log.Panicf("l must not be relative: %s", l) 142 } 143 if other.Relative { 144 log.Panicf("other must not be relative: %s", other) 145 } 146 result := l.Repo == other.Repo && pathtools.HasPrefix(other.Pkg, l.Pkg) 147 return result 148 } 149 150 // ImportPathToBazelRepoName converts a Go import path into a bazel repo name 151 // following the guidelines in http://bazel.io/docs/be/functions.html#workspace 152 func ImportPathToBazelRepoName(importpath string) string { 153 importpath = strings.ToLower(importpath) 154 components := strings.Split(importpath, "/") 155 labels := strings.Split(components[0], ".") 156 var reversed []string 157 for i := range labels { 158 l := labels[len(labels)-i-1] 159 reversed = append(reversed, l) 160 } 161 repo := strings.Join(append(reversed, components[1:]...), "_") 162 return strings.NewReplacer("-", "_", ".", "_").Replace(repo) 163 }