Skip to content

Commit cf7b6f5

Browse files
committed
Initial import
0 parents  commit cf7b6f5

9 files changed

+449
-0
lines changed

.gitignore

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Binaries for programs and plugins
2+
*.exe
3+
*.dll
4+
*.so
5+
*.dylib
6+
7+
# Test binary, build with `go test -c`
8+
*.test
9+
10+
testdata/*json
11+
12+
# Output of the go coverage tool, specifically when used with LiteIDE
13+
*.out
14+
15+
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
16+
.glide/

.travis.yml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
language: go
2+
3+
go:
4+
- 1.9
5+
- tip
6+
7+
install: true
8+
9+
matrix:
10+
allow_failures:
11+
- go: tip
12+
fast_finish: true
13+
14+
script:
15+
- make lint
16+
- make test
17+
18+
notifications:
19+
email: false

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2017
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
.PHONY: test clean lint
2+
3+
test:
4+
curl -L https://raw.githubusercontent.com/package-url/purl-test-suite/master/test-suite-data.json -o testdata/test-suite-data.json
5+
go test -v -cover ./...
6+
7+
clean:
8+
find . -name "test-suite-data.json" | xargs rm -f
9+
10+
lint:
11+
go get -u github.com/golang/lint/golint
12+
golint -set_exit_status

README.md

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# packageurl-go
2+
3+
Go implementation of the package url spec
4+
5+
[![Build Status](https://travis-ci.org/package-url/packageurl-go.svg)](https://travis-ci.org/package-url/packageurl-go)
6+
7+
8+
## Install
9+
```
10+
go get -u github.com/package-url/packageurl-go
11+
```
12+
13+
## Versioning
14+
15+
The versions will follow the spec. So if the spec is released at ``1.0``. Then all versions in the ``1.x.y`` will follow the ``1.x`` spec.
16+
17+
18+
## Usage
19+
20+
### Create from parts
21+
```go
22+
package main
23+
24+
import (
25+
"fmt"
26+
27+
"github.com/package-url/packageurl-go"
28+
)
29+
30+
func main() {
31+
instance := packageurl.NewPackageURL("test", "ok", "name", "version", nil, "")
32+
fmt.Printf("%s", instance.ToString())
33+
}
34+
```
35+
36+
### Parse from string
37+
```go
38+
package main
39+
40+
import (
41+
"fmt"
42+
43+
"github.com/package-url/packageurl-go"
44+
)
45+
46+
func main() {
47+
instance, err := packageurl.FromString("test:ok/name@version")
48+
if err != nil {
49+
panic(err)
50+
}
51+
fmt.Printf("%#v", instance)
52+
}
53+
54+
```
55+
56+
57+
## Test
58+
Testing using the normal ``go test`` command. Using ``make test`` will pull down the test fixtures shared between all package-url projects and then execute the tests.
59+
60+
```
61+
$ make test
62+
curl -L https://raw.githubusercontent.com/package-url/purl-test-suite/master/test-suite-data.json -o testdata/test-suite-data.json
63+
% Total % Received % Xferd Average Speed Time Time Time Current
64+
Dload Upload Total Spent Left Speed
65+
100 7181 100 7181 0 0 1202 0 0:00:05 0:00:05 --:--:-- 1611
66+
go test -v -cover ./...
67+
=== RUN TestFromStringExamples
68+
--- PASS: TestFromStringExamples (0.00s)
69+
=== RUN TestToStringExamples
70+
--- PASS: TestToStringExamples (0.00s)
71+
PASS
72+
coverage: 94.7% of statements
73+
ok github.com/package-url/packageurl-go 0.002s
74+
```

VERSION

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.0.0

packageurl.go

+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/*
2+
Copyright (c) the purl authors
3+
4+
Permission is hereby granted, free of charge, to any person obtaining a copy
5+
of this software and associated documentation files (the "Software"), to deal
6+
in the Software without restriction, including without limitation the rights
7+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
copies of the Software, and to permit persons to whom the Software is
9+
furnished to do so, subject to the following conditions:
10+
11+
The above copyright notice and this permission notice shall be included in all
12+
copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20+
SOFTWARE.
21+
*/
22+
23+
// Package packageurl implements the package-url spec
24+
package packageurl
25+
26+
import (
27+
"errors"
28+
"fmt"
29+
"net/url"
30+
"strings"
31+
)
32+
33+
// Qualifiers houses each key=value pair in the package url
34+
type Qualifiers map[string]string
35+
36+
// PackageURL is the struct representation of the parts that make a package url
37+
type PackageURL struct {
38+
Type string
39+
Namespace string
40+
Name string
41+
Version string
42+
Qualifiers Qualifiers
43+
Subpath string
44+
}
45+
46+
// NewPackageURL creates a new PackageURL struct instance based on input
47+
func NewPackageURL(purlType, namespace, name, version string,
48+
qualifiers Qualifiers, subpath string) *PackageURL {
49+
50+
return &PackageURL{
51+
Type: purlType,
52+
Namespace: namespace,
53+
Name: name,
54+
Version: version,
55+
Qualifiers: qualifiers,
56+
Subpath: subpath,
57+
}
58+
}
59+
60+
// ToString returns the human readable instance of the PackageURL structure.
61+
// This is the literal purl as defined by the spec.
62+
func (p *PackageURL) ToString() string {
63+
// Start with the type and a colon
64+
purl := fmt.Sprintf("%s:", p.Type)
65+
// Add namespaces if provided
66+
if p.Namespace != "" {
67+
ns := []string{}
68+
for _, item := range strings.Split(p.Namespace, "/") {
69+
ns = append(ns, url.QueryEscape(item))
70+
}
71+
purl = purl + strings.Join(ns, "/") + "/"
72+
}
73+
// The name is always required
74+
purl = purl + p.Name
75+
// If a version is provided, add it after the at symbol
76+
if p.Version != "" {
77+
purl = purl + "@" + p.Version
78+
}
79+
80+
// Iterate over qualifiers and make groups of key=value
81+
qualifiers := []string{}
82+
for k, v := range p.Qualifiers {
83+
qualifiers = append(qualifiers, fmt.Sprintf("%s=%s", k, v))
84+
}
85+
// If there one or more key=value pairs then append on the package url
86+
if len(qualifiers) != 0 {
87+
purl = purl + "?" + strings.Join(qualifiers, "&")
88+
}
89+
// Add a subpath if available
90+
if p.Subpath != "" {
91+
purl = purl + "#" + p.Subpath
92+
}
93+
return purl
94+
}
95+
96+
// FromString parses a valid package url string into a PackageURL structure
97+
func FromString(purl string) (PackageURL, error) {
98+
initialIndex := strings.Index(purl, "#")
99+
// Start with purl being stored in the remainder
100+
remainder := purl
101+
substring := ""
102+
if initialIndex != -1 {
103+
initialSplit := strings.SplitN(purl, "#", 2)
104+
remainder = initialSplit[0]
105+
rightSide := initialSplit[1]
106+
rightSide = strings.TrimLeft(rightSide, "/")
107+
rightSide = strings.TrimRight(rightSide, "/")
108+
var rightSides []string
109+
110+
for _, item := range strings.Split(rightSide, "/") {
111+
item = strings.Replace(item, ".", "", -1)
112+
item = strings.Replace(item, "..", "", -1)
113+
if item != "" {
114+
i, err := url.PathUnescape(item)
115+
if err != nil {
116+
panic(err)
117+
}
118+
rightSides = append(rightSides, i)
119+
}
120+
}
121+
substring = strings.Join(rightSides, "")
122+
}
123+
qualifiers := Qualifiers{}
124+
index := strings.LastIndex(remainder, "?")
125+
// If we don't have anything to split then return an empty result
126+
if index != -1 {
127+
qualifier := remainder[index+1:]
128+
for _, item := range strings.Split(qualifier, "&") {
129+
kv := strings.Split(item, "=")
130+
key := strings.ToLower(kv[0])
131+
// TODO
132+
// - If the `key` is `checksums`, split the `value` on ',' to create
133+
// a list of `checksums`
134+
if kv[1] == "" {
135+
continue
136+
}
137+
value, err := url.PathUnescape(kv[1])
138+
if err != nil {
139+
panic(err)
140+
}
141+
qualifiers[key] = value
142+
}
143+
remainder = remainder[:index]
144+
}
145+
146+
nextSplit := strings.SplitN(remainder, ":", 2)
147+
if len(nextSplit) != 2 {
148+
return PackageURL{}, errors.New("type is missing")
149+
}
150+
purlType := nextSplit[0]
151+
remainder = nextSplit[1]
152+
153+
index = strings.LastIndex(remainder, "/")
154+
name := remainder[index+1:]
155+
version := ""
156+
157+
atIndex := strings.Index(name, "@")
158+
if atIndex != -1 {
159+
version = name[atIndex+1:]
160+
name = name[:atIndex]
161+
}
162+
namespaces := []string{}
163+
164+
if index != -1 {
165+
remainder = remainder[:index]
166+
167+
for _, item := range strings.Split(remainder, "/") {
168+
if item != "" {
169+
unescaped, err := url.PathUnescape(item)
170+
if err != nil {
171+
panic(err)
172+
}
173+
namespaces = append(namespaces, unescaped)
174+
}
175+
}
176+
}
177+
178+
// Fail if name is empty at this point
179+
if name == "" {
180+
return PackageURL{}, errors.New("name is required")
181+
}
182+
183+
return PackageURL{
184+
Type: purlType,
185+
Namespace: strings.Join(namespaces, "/"),
186+
Name: name,
187+
Version: version,
188+
Qualifiers: qualifiers,
189+
Subpath: substring,
190+
}, nil
191+
}

0 commit comments

Comments
 (0)