Serge Bazanski | cbd02be | 2021-09-24 13:39:12 +0200 | [diff] [blame] | 1 | package toolbase |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "regexp" |
| 6 | "strings" |
| 7 | ) |
| 8 | |
| 9 | // BazelLabel is a label, as defined by Bazel's documentation: |
| 10 | // |
| 11 | // https://docs.bazel.build/versions/main/skylark/lib/Label.html |
| 12 | type BazelLabel struct { |
| 13 | WorkspaceName string |
| 14 | PackagePath []string |
| 15 | Name string |
| 16 | } |
| 17 | |
| 18 | func (b BazelLabel) Package() string { |
| 19 | return strings.Join(b.PackagePath, "/") |
| 20 | } |
| 21 | |
| 22 | func (b BazelLabel) String() string { |
| 23 | return fmt.Sprintf("@%s//%s:%s", b.WorkspaceName, b.Package(), b.Name) |
| 24 | } |
| 25 | |
| 26 | var ( |
| 27 | // reLabel splits a Bazel label into a workspace name (if set) and a |
| 28 | // workspace root relative package/name. |
| 29 | reLabel = regexp.MustCompile(`^(@[^:/]+)?//(.+)$`) |
| 30 | // rePathPart matches valid label path parts. |
| 31 | rePathPart = regexp.MustCompile(`^[^:/]+$`) |
| 32 | ) |
| 33 | |
| 34 | // ParseBazelLabel converts parses a string representation of a Bazel Label. If |
| 35 | // the given representation is invalid or for some other reason unparseable, |
| 36 | // nil is returned. |
| 37 | func ParseBazelLabel(s string) *BazelLabel { |
| 38 | res := BazelLabel{ |
| 39 | WorkspaceName: "dev_source_monogon", |
| 40 | } |
| 41 | |
| 42 | // Split label into workspace name (if set) and a workspace root relative |
| 43 | // package/name. |
| 44 | m := reLabel.FindStringSubmatch(s) |
| 45 | if m == nil { |
| 46 | return nil |
| 47 | } |
| 48 | packageRel := m[2] |
| 49 | if m[1] != "" { |
| 50 | res.WorkspaceName = m[1][1:] |
| 51 | } |
| 52 | |
| 53 | // Split path by ':', which is the target name delimiter. If it appears |
| 54 | // exactly once, interpret everything to its right as the target name. |
| 55 | targetSplit := strings.Split(packageRel, ":") |
| 56 | switch len(targetSplit) { |
| 57 | case 1: |
| 58 | case 2: |
| 59 | packageRel = targetSplit[0] |
| 60 | res.Name = targetSplit[1] |
| 61 | if !rePathPart.MatchString(res.Name) { |
| 62 | return nil |
| 63 | } |
| 64 | default: |
| 65 | return nil |
| 66 | } |
| 67 | |
| 68 | // Split the package path by /, and if the name was not explicitly given, |
| 69 | // use the last element of the package path. |
| 70 | if packageRel == "" { |
| 71 | res.PackagePath = nil |
| 72 | } else { |
| 73 | res.PackagePath = strings.Split(packageRel, "/") |
| 74 | } |
| 75 | if res.Name == "" { |
| 76 | res.Name = res.PackagePath[len(res.PackagePath)-1] |
| 77 | } |
| 78 | |
| 79 | // Ensure all parts of the package path are valid. |
| 80 | for _, p := range res.PackagePath { |
| 81 | if !rePathPart.MatchString(p) { |
| 82 | return nil |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | return &res |
| 87 | } |