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  }