Skip to content

Commit e9a348a

Browse files
committed
Auto merge of #3268 - Turbo87:license, r=locks
Parse license expressions and show links to choosealicense.com <img width="236" alt="Bildschirmfoto 2021-02-10 um 01 00 50" src="https://user-images.githubusercontent.com/141300/107444794-8bf0f900-6b3b-11eb-8518-138361431622.png"> <img width="261" alt="Bildschirmfoto 2021-02-10 um 01 01 05" src="https://user-images.githubusercontent.com/141300/107444797-8d222600-6b3b-11eb-833c-249a2ff744be.png"> This PR is implementing a basic SPDX license expression parser and a corresponding `LicenseExpression` component, which deemphasizes keywords and adds links to https://choosealicense.com for known license IDs.
2 parents f684d3e + 098ac16 commit e9a348a

File tree

7 files changed

+151
-3
lines changed

7 files changed

+151
-3
lines changed

app/components/crate-sidebar.hbs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@
8282
{{#if @version.license}}
8383
<div>
8484
<h3>License</h3>
85-
<p data-test-license>{{ @version.license }}</p>
85+
<p data-test-license>
86+
<LicenseExpression @license={{@version.license }} />
87+
</p>
8688
</div>
8789
{{/if}}
8890

app/components/license-expression.hbs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{{#each (parse-license @license) as |part|}}
2+
{{#if part.isKeyword}}
3+
<small>{{part.text}}</small>
4+
{{else if part.link}}
5+
<a href={{part.link}} rel="noreferrer">
6+
{{part.text}}
7+
</a>
8+
{{else}}
9+
{{part.text}}
10+
{{/if}}
11+
{{/each}}

app/components/version-list/row.hbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262

6363
<span local-class="license">
6464
{{svg-jar "license"}}
65-
{{@version.license}}
65+
<LicenseExpression @license={{@version.license}} />
6666
</span>
6767

6868
{{#if @version.featureList}}

app/helpers/parse-license.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { helper } from '@ember/component/helper';
2+
3+
import { parseLicense } from '../utils/license';
4+
5+
export default helper(function ([expression]) {
6+
if (expression) {
7+
return parseLicense(expression);
8+
}
9+
});

app/utils/license.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// see https://choosealicense.com/appendix/
2+
const CAL_LICENSES = [
3+
'0bsd',
4+
'afl-3.0',
5+
'agpl-3.0',
6+
'apache-2.0',
7+
'artistic-2.0',
8+
'bsd-2-clause',
9+
'bsd-3-clause-clear',
10+
'bsd-3-clause',
11+
'bsd-4-clause',
12+
'bsl-1.0',
13+
'cc-by-4.0',
14+
'cc-by-sa-4.0',
15+
'cc0-1.0',
16+
'cecill-2.1',
17+
'ecl-2.0',
18+
'epl-1.0',
19+
'epl-2.0',
20+
'eupl-1.1',
21+
'eupl-1.2',
22+
'gpl-2.0',
23+
'gpl-3.0',
24+
'isc',
25+
'lgpl-2.1',
26+
'lgpl-3.0',
27+
'lppl-1.3c',
28+
'mit',
29+
'mpl-2.0',
30+
'ms-pl',
31+
'ms-rl',
32+
'ncsa',
33+
'odbl-1.0',
34+
'ofl-1.1',
35+
'osl-3.0',
36+
'postgresql',
37+
'unlicense',
38+
'upl-1.0',
39+
'vim',
40+
'wtfpl',
41+
'zlib',
42+
];
43+
44+
const LICENSE_KEYWORDS = new Set(['OR', 'AND', 'WITH']);
45+
46+
export function parseLicense(text) {
47+
return text
48+
.trim()
49+
.replace('/', ' OR ')
50+
.replace(/ +/g, ' ')
51+
.split(' ')
52+
.map(text => {
53+
let lowerCaseText = text.toLowerCase();
54+
55+
let isKeyword = LICENSE_KEYWORDS.has(text);
56+
let calLicense = CAL_LICENSES.find(it => it === lowerCaseText);
57+
let link = calLicense ? `https://choosealicense.com/licenses/${calLicense}` : undefined;
58+
59+
return { isKeyword, text, link };
60+
});
61+
}

tests/acceptance/crate-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ module('Acceptance | crate page', function (hooks) {
184184
assert.dom('[data-test-license]').hasText('Apache-2.0');
185185

186186
await click('[data-test-version-link="0.5.0"]');
187-
assert.dom('[data-test-license]').hasText('MIT/Apache-2.0');
187+
assert.dom('[data-test-license]').hasText('MIT OR Apache-2.0');
188188
});
189189

190190
skip('crates can be yanked by owner', async function (assert) {

tests/utils/license-test.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { module, test } from 'qunit';
2+
3+
import { parseLicense } from '../../utils/license';
4+
5+
module('parseLicense()', function () {
6+
const TESTS = [
7+
['MIT', [{ isKeyword: false, link: 'https://choosealicense.com/licenses/mit', text: 'MIT' }]],
8+
[
9+
'MIT OR Apache-2.0',
10+
[
11+
{ isKeyword: false, link: 'https://choosealicense.com/licenses/mit', text: 'MIT' },
12+
{ isKeyword: true, link: undefined, text: 'OR' },
13+
{ isKeyword: false, link: 'https://choosealicense.com/licenses/apache-2.0', text: 'Apache-2.0' },
14+
],
15+
],
16+
[
17+
'MIT/Apache-2.0',
18+
[
19+
{ isKeyword: false, link: 'https://choosealicense.com/licenses/mit', text: 'MIT' },
20+
{ isKeyword: true, link: undefined, text: 'OR' },
21+
{ isKeyword: false, link: 'https://choosealicense.com/licenses/apache-2.0', text: 'Apache-2.0' },
22+
],
23+
],
24+
[
25+
'LGPL-2.1-only AND MIT AND BSD-2-Clause',
26+
[
27+
{ isKeyword: false, link: undefined, text: 'LGPL-2.1-only' },
28+
{ isKeyword: true, link: undefined, text: 'AND' },
29+
{ isKeyword: false, link: 'https://choosealicense.com/licenses/mit', text: 'MIT' },
30+
{ isKeyword: true, link: undefined, text: 'AND' },
31+
{ isKeyword: false, link: 'https://choosealicense.com/licenses/bsd-2-clause', text: 'BSD-2-Clause' },
32+
],
33+
],
34+
[
35+
'GPL-2.0-or-later WITH Bison-exception-2.2',
36+
[
37+
{ isKeyword: false, link: undefined, text: 'GPL-2.0-or-later' },
38+
{ isKeyword: true, link: undefined, text: 'WITH' },
39+
{ isKeyword: false, link: undefined, text: 'Bison-exception-2.2' },
40+
],
41+
],
42+
[
43+
'Unlicense OR MIT',
44+
[
45+
{ isKeyword: false, link: 'https://choosealicense.com/licenses/unlicense', text: 'Unlicense' },
46+
{ isKeyword: true, link: undefined, text: 'OR' },
47+
{ isKeyword: false, link: 'https://choosealicense.com/licenses/mit', text: 'MIT' },
48+
],
49+
],
50+
[
51+
'A OR B',
52+
[
53+
{ isKeyword: false, link: undefined, text: 'A' },
54+
{ isKeyword: true, link: undefined, text: 'OR' },
55+
{ isKeyword: false, link: undefined, text: 'B' },
56+
],
57+
],
58+
];
59+
60+
for (let [input, expectation] of TESTS) {
61+
test(input, function (assert) {
62+
assert.deepEqual(parseLicense(input), expectation);
63+
});
64+
}
65+
});

0 commit comments

Comments
 (0)