Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions ecosystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@ import (
)

const (
ecosystemAlpine = "alpine"
ecosystemArch = "arch"
ecosystemComposer = "composer"
ecosystemGit = "git"
ecosystemGitHubActions = "github-actions"
ecosystemGolang = "golang"
ecosystemMaven = "maven"
ecosystemNPM = "npm"
ecosystemPackagist = "packagist"
ecosystemRubyGems = "rubygems"
ecosystemSwift = "swift"
purlTypeGem = "gem"
purlTypeGitHubActions = "githubactions"
ecosystemAlpine = "alpine"
ecosystemArch = "arch"
ecosystemComposer = "composer"
ecosystemGit = "git"
ecosystemGitHubActions = "github-actions"
ecosystemGolang = "golang"
ecosystemMaven = "maven"
ecosystemNPM = "npm"
ecosystemPackagist = "packagist"
ecosystemRubyGems = "rubygems"
ecosystemSwift = "swift"
purlTypeGem = "gem"
purlTypeGitHubActions = "githubactions"
purlTypeVSCodeExtension = "vscode-extension"
)

// purlTypeForEcosystem maps ecosystem names to PURL types.
Expand Down
12 changes: 12 additions & 0 deletions makepurl.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ func normalizeComponents(purlType, namespace, name, version, registryURL string)
Qualifiers: q,
}
_ = p.Normalize()
lowercaseCaseInsensitive(&p)
return p.Namespace, p.Name, p.Version
}

Expand Down Expand Up @@ -234,6 +235,8 @@ func splitNamespace(ecosystem, name string) (namespace, pkgName string, ok bool)
pkgName = rest
}
}
case purlTypeVSCodeExtension:
return splitVSCodeExtension(name)
case ecosystemSwift:
i := strings.LastIndexByte(name, '/')
if i <= 0 || i == len(name)-1 {
Expand All @@ -249,6 +252,15 @@ func splitNamespace(ecosystem, name string) (namespace, pkgName string, ok bool)
return
}

// splitVSCodeExtension splits VS Code's publisher.name extension identifier.
func splitVSCodeExtension(name string) (publisher, extension string, ok bool) {
publisher, extension, ok = strings.Cut(name, ".")
if !ok || publisher == "" || extension == "" {
return "", "", false
}
return publisher, extension, true
}

// writeComponentEscaped writes s to b, percent-encoding characters that are not safe
// in PURL path components (namespace, name, version).
func writeComponentEscaped(b *strings.Builder, s string) {
Expand Down
11 changes: 11 additions & 0 deletions purl.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,19 @@ func Parse(s string) (*PURL, error) {
p.Name = path + "/" + p.Name
}
}
lowercaseCaseInsensitive(&p)
return &PURL{p}, nil
}

// lowercaseCaseInsensitive lowercases components the spec declares
// case-insensitive where packageurl-go's Normalize leaves them as given.
func lowercaseCaseInsensitive(p *packageurl.PackageURL) {
if p.Type == purlTypeVSCodeExtension {
p.Namespace = strings.ToLower(p.Namespace)
p.Name = strings.ToLower(p.Name)
}
}

// New creates a new PURL from components.
//
// The result is normalized with the same per-type rules Parse applies
Expand All @@ -57,6 +67,7 @@ func New(purlType, namespace, name, version string, qualifiers map[string]string
}
p := packageurl.NewPackageURL(purlType, namespace, name, version, q, "")
_ = p.Normalize()
lowercaseCaseInsensitive(p)
return &PURL{*p}
}

Expand Down
55 changes: 55 additions & 0 deletions vscode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package purl_test

import (
"testing"

"github.com/git-pkgs/purl"
)

func TestVSCodeExtensionCase(t *testing.T) {
const want = "pkg:vscode-extension/ms-python/python@2023.25.10292213"
parsed, err := purl.Parse("pkg:vscode-extension/MS-Python/Python@2023.25.10292213")
if err != nil {
t.Fatal(err)
}
for _, got := range []string{
parsed.String(),
purl.New("vscode-extension", "MS-Python", "Python", "2023.25.10292213", nil).String(),
purl.MakePURL("vscode-extension", "MS-Python.Python", "2023.25.10292213").String(),
purl.MakePURLString("vscode-extension", "MS-Python.Python", "2023.25.10292213"),
purl.BuildPURLString("vscode-extension", "MS-Python.Python", "2023.25.10292213", ""),
} {
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
}

func TestVSCodeExtensionOpenVSX(t *testing.T) {
const want = "pkg:vscode-extension/muhammad-sammy/csharp@2.15.30-RC1?platform=linux-x64&repository_url=https:%2F%2Fopen-vsx.org"
parsed, err := purl.Parse("pkg:vscode-extension/Muhammad-Sammy/CSharp@2.15.30-RC1?platform=linux-x64&repository_url=https:%2F%2Fopen-vsx.org")
if err != nil {
t.Fatal(err)
}
if got := parsed.String(); got != want {
t.Errorf("Parse() = %q, want %q", got, want)
}
if !parsed.IsPrivateRegistry() {
t.Error("open-vsx.org should not be the default registry")
}
built := purl.BuildPURLString("vscode-extension", "Muhammad-Sammy.CSharp", "2.15.30", "https://open-vsx.org")
if built != "pkg:vscode-extension/muhammad-sammy/csharp@2.15.30?repository_url=https:%2F%2Fopen-vsx.org" {
t.Errorf("BuildPURLString() = %q", built)
}
}

func TestVSCodeExtensionRequiresPublisher(t *testing.T) {
for _, name := range []string{"python", ".python", "ms-python.", "ms-python/python"} {
if got := purl.MakePURLString("vscode-extension", name, "1.0.0"); got != "" {
t.Errorf("MakePURLString(%q) = %q, want empty", name, got)
}
if got := purl.MakePURL("vscode-extension", name, "1.0.0"); got != nil {
t.Errorf("MakePURL(%q) = %q, want nil", name, got)
}
}
}
Loading