Skip to content

Commit

Permalink
refactor package name parsers
Browse files Browse the repository at this point in the history
  • Loading branch information
dmgk committed Oct 22, 2019
1 parent a0cdb41 commit 3b6b2ea
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 128 deletions.
9 changes: 4 additions & 5 deletions tuple/mirrors.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,9 @@ var mirrors = map[string]struct {
"sigs.k8s.io/yaml": {source: GH{}, account: "kubernetes-sigs", project: "yaml"},
}

func (t *Tuple) fromMirror() bool {
if m, ok := mirrors[t.Package]; ok {
t.setSource(m.source, m.account, m.project)
return true
func tryMirror(pkg, packagePrefix string) (*Tuple, error) {
if m, ok := mirrors[pkg]; ok {
return newTuple(m.source, pkg, m.account, m.project, packagePrefix), nil
}
return false
return nil, nil
}
190 changes: 114 additions & 76 deletions tuple/tuple.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,119 +190,157 @@ var versionRx = regexp.MustCompile(`\A(v\d+\.\d+\.\d+(?:-[0-9A-Za-z]+[0-9A-Za-z\
// v0.8.0-dev.2.0.20180608203834-19279f049241
var tagRx = regexp.MustCompile(`\Av\d+\.\d+\.\d+-(?:[0-9A-Za-z\.]+\.)?\d{14}-([0-9a-f]+)\z`)

// Parse parses a package spec into Tuple.
func Parse(spec, packagePrefix string) (*Tuple, error) {
t, err := parseReplaceSpec(spec, packagePrefix)
if err != nil {
return nil, err
}
if t != nil {
// Tuple was parsed from a replace spec
return t, nil
}
return parseSpec(spec, packagePrefix)
}

func parseReplaceSpec(spec, packagePrefix string) (*Tuple, error) {
const replaceOp = " => "

// Replaced package spec
if strings.Contains(spec, replaceOp) {
replaceSpecs := strings.Split(spec, replaceOp)
if len(replaceSpecs) != 2 {
return nil, fmt.Errorf("unexpected number of packages in replace spec: %q", spec)
}
if !strings.Contains(spec, replaceOp) {
// Not a replace spec
return nil, nil
}

var sourcePkg string
sourceParts := strings.Fields(replaceSpecs[0])
switch len(sourceParts) {
case 1, 2:
sourcePkg = sourceParts[0]
default:
return nil, fmt.Errorf("unexpected number of fields in the replace spec source: %q", spec)
}
replaceSpecs := strings.Split(spec, replaceOp)
if len(replaceSpecs) != 2 {
return nil, fmt.Errorf("unexpected number of packages in replace spec: %q", spec)
}

targetParts := strings.Fields(replaceSpecs[1])
switch len(targetParts) {
case 1:
if targetParts[0][0] == '.' || targetParts[0][0] == '/' {
// Target package spec is local filesystem path
return nil, ReplacementLocalFilesystemError(spec)
}
// Target package spec is missing commit ID/tag
return nil, ReplacementMissingCommitError(spec)
case 2:
// OK
default:
return nil, fmt.Errorf("unexpected number of fields in the replace spec target: %q", spec)
}
var sourcePkg string

tuple, err := Parse(replaceSpecs[1], packagePrefix)
if err != nil {
return nil, err
}
sourceFields := strings.Fields(replaceSpecs[0])
switch len(sourceFields) {
case 1, 2:
sourcePkg = sourceFields[0]
default:
return nil, fmt.Errorf("unexpected number of fields in the replace spec source: %q", spec)
}

// Keep the target package's account/project/tag but set the source package name
tuple.Package = sourcePkg
targetFields := strings.Fields(replaceSpecs[1])
switch len(targetFields) {
case 1:
if targetFields[0][0] == '.' || targetFields[0][0] == '/' {
// Target package spec is a local filesystem path
return nil, ReplacementLocalFilesystemError(spec)
}
// Target package spec is missing commit ID/tag
return nil, ReplacementMissingCommitError(spec)
case 2:
// OK
default:
return nil, fmt.Errorf("unexpected number of fields in the replace spec target: %q", spec)
}

return tuple, nil
t, err := parseSpec(replaceSpecs[1], packagePrefix)
if err != nil {
return nil, err
}

// Regular package spec
// Keep the target package's account/project/tag but set the source package name
t.Package = sourcePkg

return t, nil
}

func parseSpec(spec, packagePrefix string) (*Tuple, error) {
fields := strings.Fields(spec)
if len(fields) != 2 {
return nil, fmt.Errorf("unexpected number of fields: %q", spec)
}

pkg := fields[0]
version := fields[1]
tuple := &Tuple{Package: pkg, Prefix: packagePrefix}

if !tuple.fromMirror() {
switch {
case strings.HasPrefix(pkg, "github.com"):
if err := tuple.fromGithub(); err != nil {
return nil, err
}
case strings.HasPrefix(pkg, "gitlab.com"):
if err := tuple.fromGitlab(); err != nil {
return nil, err
}
default:
tuple.fromVanity()

pkgParsers := []func(string, string) (*Tuple, error){
tryMirror,
tryGithub,
tryGitlab,
tryVanity,
noMatch,
}

var t *Tuple
for _, fn := range pkgParsers {
var err error
t, err = fn(pkg, packagePrefix)
if err != nil {
return nil, err
}
if t != nil {
break
}
}

switch {
case tagRx.MatchString(version):
sm := tagRx.FindAllStringSubmatch(version, -1)
tuple.Tag = sm[0][1]
t.Tag = sm[0][1]
case versionRx.MatchString(version):
sm := versionRx.FindAllStringSubmatch(version, -1)
tuple.Tag = sm[0][1]
t.Tag = sm[0][1]
default:
return nil, fmt.Errorf("unexpected version string: %q", version)
}

if tuple.Source == nil {
return nil, SourceError(tuple.String())
if t.Source == nil {
return nil, SourceError(t.String())
}

return tuple, nil
return t, nil
}

var groupRe = regexp.MustCompile(`[^\w]+`)

func (t *Tuple) setSource(source Source, account, project string) {
t.Source = source
t.Account = account
t.Project = project
group := account + "_" + project
group = groupRe.ReplaceAllString(group, "_")
t.Group = strings.ToLower(group)
func tryGithub(pkg, packagePrefix string) (*Tuple, error) {
if !strings.HasPrefix(pkg, "github.com") {
return nil, nil
}
parts := strings.Split(pkg, "/")
if len(parts) < 3 {
return nil, fmt.Errorf("unexpected Github package name: %q", pkg)
}
return newTuple(GH{}, pkg, parts[1], parts[2], packagePrefix), nil
}

func (t *Tuple) fromGithub() error {
parts := strings.Split(t.Package, "/")
func tryGitlab(pkg, packagePrefix string) (*Tuple, error) {
if !strings.HasPrefix(pkg, "gitlab.com") {
return nil, nil
}
parts := strings.Split(pkg, "/")
if len(parts) < 3 {
return fmt.Errorf("unexpected Github package name: %q", t.Package)
return nil, fmt.Errorf("unexpected Gitlab package name: %q", pkg)
}
t.setSource(GH{}, parts[1], parts[2])
return nil
return newTuple(GL{}, pkg, parts[1], parts[2], packagePrefix), nil
}

func (t *Tuple) fromGitlab() error {
parts := strings.Split(t.Package, "/")
if len(parts) < 3 {
return fmt.Errorf("unexpected Gitlab package name: %q", t.Package)
// noMatch returns "unparsed" tuple as a fallback.
func noMatch(pkg, packagePrefix string) (*Tuple, error) {
return newTuple(nil, pkg, "", "", packagePrefix), nil
}

var groupRe = regexp.MustCompile(`[^\w]+`)

func newTuple(source Source, pkg, account, project, packagePrefix string) *Tuple {
t := &Tuple{
Source: source,
Package: pkg,
Account: account,
Project: project,
Prefix: packagePrefix,
}
if t.Account != "" && t.Project != "" {
group := t.Account + "_" + t.Project
group = groupRe.ReplaceAllString(group, "_")
t.Group = strings.ToLower(group)
} else {
t.Group = "group_name"
}
t.setSource(GL{}, parts[1], parts[2])
return nil
return t
}
4 changes: 2 additions & 2 deletions tuple/tuple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ GL_TUPLE= \
gitlab-org:gitaly-proto:v1.32.0:gitlab_org_gitaly_proto/vendor/gitlab.com/gitlab-org/gitaly-proto \
gitlab-org:labkit:0c3fc7cdd57c:gitlab_org_labkit/vendor/gitlab.com/gitlab-org/labkit
# Mirrors for the following packages are not currently known, please look them up and handle these tuples manually:
# ::v1.0.0:/vendor/another.vanity_url.org/account/project
# ::v1.2.3:/vendor/some_unknown.vanity_url.net/account/project
# ::v1.0.0:group_name/vendor/another.vanity_url.org/account/project
# ::v1.2.3:group_name/vendor/some_unknown.vanity_url.net/account/project
`

tt, err := NewParser("vendor", true).Read(strings.NewReader(given))
Expand Down
78 changes: 36 additions & 42 deletions tuple/vanity.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package tuple

import "regexp"

type vanityParser func(*Tuple) bool
type vanityParser func(string, string) *Tuple

var vanity = map[string]vanityParser{
"go.mozilla.org": goMozillaOrgParser,
Expand All @@ -12,91 +12,85 @@ var vanity = map[string]vanityParser{
"k8s.io": k8sIoParser,
}

func (t *Tuple) fromVanity() bool {
func tryVanity(pkg, packagePrefix string) (*Tuple, error) {
for _, fn := range vanity {
if fn(t) {
return true
if t := fn(pkg, packagePrefix); t != nil {
return t, nil
}
}
return false
return nil, nil
}

// go.mozilla.org/gopgagent -> github.com/mozilla-services/gopgagent
var goMozillaOrgRe = regexp.MustCompile(`\Ago\.mozilla\.org/([0-9A-Za-z][-0-9A-Za-z]+)\z`)

func goMozillaOrgParser(t *Tuple) bool {
if !goMozillaOrgRe.MatchString(t.Package) {
return false
func goMozillaOrgParser(pkg, packagePrefix string) *Tuple {
if !goMozillaOrgRe.MatchString(pkg) {
return nil
}
sm := goMozillaOrgRe.FindAllStringSubmatch(t.Package, -1)
sm := goMozillaOrgRe.FindAllStringSubmatch(pkg, -1)
if len(sm) == 0 {
return false
return nil
}
t.setSource(GH{}, "mozilla-services", sm[0][1])
return true
return newTuple(GH{}, pkg, "mozilla-services", sm[0][1], packagePrefix)
}

// go.uber.org/zap -> github.com/uber-go/zap
var goUberOrgRe = regexp.MustCompile(`\Ago\.uber\.org/([0-9A-Za-z][-0-9A-Za-z]+)\z`)

func goUberOrgParser(t *Tuple) bool {
if !goUberOrgRe.MatchString(t.Package) {
return false
func goUberOrgParser(pkg, packagePrefix string) *Tuple {
if !goUberOrgRe.MatchString(pkg) {
return nil
}
sm := goUberOrgRe.FindAllStringSubmatch(t.Package, -1)
sm := goUberOrgRe.FindAllStringSubmatch(pkg, -1)
if len(sm) == 0 {
return false
return nil
}
t.setSource(GH{}, "uber-go", sm[0][1])
return true
return newTuple(GH{}, pkg, "uber-go", sm[0][1], packagePrefix)
}

// golang.org/x/pkg -> github.com/golang/pkg
var golangOrgRe = regexp.MustCompile(`\Agolang\.org/x/([0-9A-Za-z][-0-9A-Za-z]+)\z`)

func golangOrgParser(t *Tuple) bool {
if !golangOrgRe.MatchString(t.Package) {
return false
func golangOrgParser(pkg, packagePrefix string) *Tuple {
if !golangOrgRe.MatchString(pkg) {
return nil
}
sm := golangOrgRe.FindAllStringSubmatch(t.Package, -1)
sm := golangOrgRe.FindAllStringSubmatch(pkg, -1)
if len(sm) == 0 {
return false
return nil
}
t.setSource(GH{}, "golang", sm[0][1])
return true
return newTuple(GH{}, pkg, "golang", sm[0][1], packagePrefix)
}

// gopkg.in/pkg.v3 -> github.com/go-pkg/pkg
// gopkg.in/user/pkg.v3 -> github.com/user/pkg
var gopkgInRe = regexp.MustCompile(`\Agopkg\.in/([0-9A-Za-z][-0-9A-Za-z]+)(?:\.v.+)?(?:/([0-9A-Za-z][-0-9A-Za-z]+)(?:\.v.+))?\z`)

func gopkgInParser(t *Tuple) bool {
if !gopkgInRe.MatchString(t.Package) {
return false
func gopkgInParser(pkg, packagePrefix string) *Tuple {
if !gopkgInRe.MatchString(pkg) {
return nil
}
sm := gopkgInRe.FindAllStringSubmatch(t.Package, -1)
sm := gopkgInRe.FindAllStringSubmatch(pkg, -1)
if len(sm) == 0 {
return false
return nil
}
if sm[0][2] == "" {
t.setSource(GH{}, "go-"+sm[0][1], sm[0][1])
} else {
t.setSource(GH{}, sm[0][1], sm[0][2])
return newTuple(GH{}, pkg, "go-"+sm[0][1], sm[0][1], packagePrefix)
}
return true
return newTuple(GH{}, pkg, sm[0][1], sm[0][2], packagePrefix)
}

// k8s.io/api -> github.com/kubernetes/api
var k8sIoRe = regexp.MustCompile(`\Ak8s\.io/([0-9A-Za-z][-0-9A-Za-z]+)\z`)

func k8sIoParser(t *Tuple) bool {
if !k8sIoRe.MatchString(t.Package) {
return false
func k8sIoParser(pkg, packagePrefix string) *Tuple {
if !k8sIoRe.MatchString(pkg) {
return nil
}
sm := k8sIoRe.FindAllStringSubmatch(t.Package, -1)
sm := k8sIoRe.FindAllStringSubmatch(pkg, -1)
if len(sm) == 0 {
return false
return nil
}
t.setSource(GH{}, "kubernetes", sm[0][1])
return true
return newTuple(GH{}, pkg, "kubernetes", sm[0][1], packagePrefix)
}
Loading

0 comments on commit 3b6b2ea

Please sign in to comment.