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  }