Skip to content

Commit

Permalink
Merge pull request #73 from package-url/jdalton/sync
Browse files Browse the repository at this point in the history
Additional feat, chore, and fixes
  • Loading branch information
jdalton authored Aug 16, 2024
2 parents 400de0c + b6c8ce8 commit b5660a5
Show file tree
Hide file tree
Showing 19 changed files with 1,260 additions and 956 deletions.
135 changes: 97 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,73 +1,75 @@
# packageurl-js

### Installing:
### Installing

To install `packageurl-js` in your project, simply run:
```
```bash
npm install packageurl-js
```

This command will download the `packageurl-js` npm package for use in your application.

### Local Development:
Clone the `packageurl-js` repo and `cd` into the directory.
### Local Development

Then run:
```
Clone the `packageurl-js` repo and `cd` into the directory.

Then run:
```bash
npm install
```

### Testing
To run the test suite:
```

To run the test suite:
```bash
npm test
```

### Usage Examples

#### Import ES6 Module
#### Importing

As an ES6 module
```js
import { PackageURL } from 'packageurl-js'
```
import { PackageURL } from 'packageurl-js';
```

#### Import CommonJs Module

```
const { PackageURL } = require('packageurl-js');
As a CommonJS module
```js
const { PackageURL } = require('packageurl-js')
```

#### Parsing from a string
#### Parsing

```
const pkg = PackageURL.fromString('pkg:maven/org.springframework.integration/[email protected]');
console.log(pkg);
```js
const purlStr = 'pkg:maven/org.springframework.integration/[email protected]'
console.log(PackageURL.fromString(purlStr))
console.log(new PackageURL(...PackageURL.parseString(purlStr)))
```

=>
will both log

```
PackageURL {
type: 'maven',
name: 'spring-integration-jms',
namespace: 'org.springframework.integration',
version: '5.5.5',
qualifiers: null,
subpath: null
type: 'maven',
name: 'spring-integration-jms',
namespace: 'org.springframework.integration',
version: '5.5.5',
qualifiers: undefined,
subpath: undefined
}
```

#### Constructing

```
```js
const pkg = new PackageURL(
'maven',
'org.springframework.integration',
'spring-integration-jms',
'5.5.5',
undefined,
undefined);
console.log(pkg.toString());
'5.5.5'
)
console.log(pkg.toString())
```

=>
Expand All @@ -78,16 +80,73 @@ pkg:maven/org.springframework.integration/[email protected]

#### Error Handling

```
```js
try {
PackageURL.fromString('not-a-purl');
} catch(ex) {
console.error(ex.message);
PackageURL.fromString('not-a-purl')
} catch (e) {
console.error(e.message)
}
```

=>

```
purl is missing the required "pkg" scheme component.
```
Invalid purl: missing required "pkg" scheme component
```

#### Helper Objects

Helpers for encoding, normalizing, and validating purl components and types can
be imported directly from the module or found on the PackageURL class as static
properties.
```js
import {
PackageURL,
PurlComponent,
PurlType
} from 'packageurl-js'

PurlComponent === PackageURL.Component // => true
PurlType === PackageURL.Type // => true
```

#### PurlComponent

Contains the following properties each with their own `encode`, `normalize`,
and `validate` methods, e.g. `PurlComponent.name.validate(nameStr)`:
- type
- namespace
- name
- version
- qualifiers
- qualifierKey
- qualifierValue
- subpath

#### PurlType

Contains the following properties each with their own `normalize`, and `validate`
methods, e.g. `PurlType.npm.validate(purlObj)`:
- alpm
- apk
- bitbucket
- bitnami
- composer
- conan
- cran
- deb
- github
- gitlab
- golang
- hex
- huggingface
- luarocks
- maven
- mlflow
- npm
- oci
- pub
- pypi
- qpkg
- rpm
- swift
12 changes: 6 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ SOFTWARE.
'use strict'

const {
Component,
KnownQualifierNames,
PackageURL,
Type
PurlComponent,
PurlQualifierNames,
PurlType
} = require('./src/package-url')

module.exports = {
Component,
KnownQualifierNames,
PackageURL,
Type
PurlComponent,
PurlQualifierNames,
PurlType
}
7 changes: 7 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict'

const LOOP_SENTINEL = 1_000_000

module.exports = {
LOOP_SENTINEL
}
71 changes: 71 additions & 0 deletions src/encode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use strict'

const { isObject } = require('./objects')
const { isNonEmptyString } = require('./strings')

const reusedSearchParams = new URLSearchParams()
const reusedSearchParamKey = '_'
const reusedSearchParamOffset = 2 // '_='.length

const { encodeURIComponent } = globalThis

function encodeNamespace(namespace) {
return isNonEmptyString(namespace)
? encodeURIComponent(namespace)
.replace(/%3A/g, ':')
.replace(/%2F/g, '/')
: ''
}

function encodeQualifierParam(param) {
if (isNonEmptyString(param)) {
// Param key and value are encoded with `percentEncodeSet` of
// 'application/x-www-form-urlencoded' and `spaceAsPlus` of `true`.
// https://url.spec.whatwg.org/#urlencoded-serializing
reusedSearchParams.set(reusedSearchParamKey, param)
return replacePlusSignWithPercentEncodedSpace(
reusedSearchParams.toString().slice(reusedSearchParamOffset)
)
}
return ''
}

function encodeQualifiers(qualifiers) {
if (isObject(qualifiers)) {
// Sort this list of qualifier strings lexicographically.
const qualifiersKeys = Object.keys(qualifiers).sort()
const searchParams = new URLSearchParams()
for (let i = 0, { length } = qualifiersKeys; i < length; i += 1) {
const key = qualifiersKeys[i]
searchParams.set(key, qualifiers[key])
}
return replacePlusSignWithPercentEncodedSpace(searchParams.toString())
}
return ''
}

function encodeSubpath(subpath) {
return isNonEmptyString(subpath)
? encodeURIComponent(subpath).replace(/%2F/g, '/')
: ''
}

function encodeVersion(version) {
return isNonEmptyString(version)
? encodeURIComponent(version).replace(/%3A/g, ':').replace(/%2B/g, '+')
: ''
}

function replacePlusSignWithPercentEncodedSpace(str) {
// Convert plus signs to %20 for better portability.
return str.replace(/\+/g, '%20')
}

module.exports = {
encodeNamespace,
encodeVersion,
encodeQualifiers,
encodeQualifierParam,
encodeSubpath,
encodeURIComponent
}
32 changes: 32 additions & 0 deletions src/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict'

function createHelpersNamespaceObject(helpers, options_ = {}) {
const { comparator, ...defaults } = { __proto__: null, ...options_ }
const helperNames = Object.keys(helpers).sort()
const propNames = [
...new Set([...Object.values(helpers)].map(Object.keys).flat())
].sort(comparator)
const nsObject = Object.create(null)
for (let i = 0, { length } = propNames; i < length; i += 1) {
const propName = propNames[i]
const helpersForProp = Object.create(null)
for (
let j = 0, { length: length_j } = helperNames;
j < length_j;
j += 1
) {
const helperName = helperNames[j]
const helperValue =
helpers[helperName][propName] ?? defaults[helperName]
if (helperValue !== undefined) {
helpersForProp[helperName] = helperValue
}
}
nsObject[propName] = helpersForProp
}
return nsObject
}

module.exports = {
createHelpersNamespaceObject
}
13 changes: 13 additions & 0 deletions src/lang.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use strict'

function isNullishOrEmptyString(value) {
return (
value === null ||
value === undefined ||
(typeof value === 'string' && value.length === 0)
)
}

module.exports = {
isNullishOrEmptyString
}
Loading

0 comments on commit b5660a5

Please sign in to comment.