diff --git a/services/pypi/pypi-django-versions.service.js b/services/pypi/pypi-django-versions.service.js index ffe0fbf1bf37fea556498385a400470ddb2e9ca8..da07cf8f0cf9c385b6171c61c594f9b8023ace4b 100644 --- a/services/pypi/pypi-django-versions.service.js +++ b/services/pypi/pypi-django-versions.service.js @@ -1,45 +1,12 @@ -import PypiBase from './pypi-base.js' -import { sortDjangoVersions, parseClassifiers } from './pypi-helpers.js' - -export default class PypiDjangoVersions extends PypiBase { - static category = 'platform-support' - - static route = this.buildRoute('pypi/djversions') - - static examples = [ - { - title: 'PyPI - Django Version', - pattern: ':packageName', - namedParams: { packageName: 'djangorestframework' }, - staticPreview: this.render({ versions: ['1.11', '2.0', '2.1'] }), - keywords: ['python'], - }, - ] - - static defaultBadgeData = { label: 'django versions' } - - static render({ versions }) { - if (versions.length > 0) { - return { - message: sortDjangoVersions(versions).join(' | '), - color: 'blue', - } - } else { - return { - message: 'missing', - color: 'red', - } - } - } - - async handle({ egg }) { - const packageData = await this.fetch({ egg }) - - const versions = parseClassifiers( - packageData, - /^Framework :: Django :: ([\d.]+)$/ - ) - - return this.constructor.render({ versions }) - } -} +import { redirector } from '../index.js' + +export default redirector({ + category: 'platform-support', + route: { + base: 'pypi/djversions', + pattern: ':packageName*', + }, + transformPath: ({ packageName }) => + `/pypi/frameworkversions/django/${packageName}`, + dateAdded: new Date('2022-07-28'), +}) diff --git a/services/pypi/pypi-django-versions.tester.js b/services/pypi/pypi-django-versions.tester.js index 83fdca15ec086c52792ba4f2114ada2c86b0c0fa..adba0a5e2b8927f2cf258a752358a6dcf054c8de 100644 --- a/services/pypi/pypi-django-versions.tester.js +++ b/services/pypi/pypi-django-versions.tester.js @@ -1,32 +1,24 @@ -import Joi from 'joi' import { createServiceTester } from '../tester.js' export const t = await createServiceTester() -const isPipeSeparatedDjangoVersions = Joi.string().regex( - /^([1-9]\.[0-9]+(?: \| )?)+$/ +t.create( + 'redirect supported django versions (valid, package version in request)' ) - -t.create('supported django versions (valid, package version in request)') .get('/djangorestframework/3.7.3.json') - .expectBadge({ - label: 'django versions', - message: isPipeSeparatedDjangoVersions, - }) + .expectRedirect( + '/pypi/frameworkversions/django/djangorestframework/3.7.3.json' + ) -t.create('supported django versions (valid, no package version specified)') +t.create( + 'redirect supported django versions (valid, no package version specified)' +) .get('/djangorestframework.json') - .expectBadge({ - label: 'django versions', - message: isPipeSeparatedDjangoVersions, - }) + .expectRedirect('/pypi/frameworkversions/django/djangorestframework.json') -t.create('supported django versions (no versions specified)') +t.create('redirect supported django versions (no versions specified)') .get('/django/1.11.json') - .expectBadge({ label: 'django versions', message: 'missing' }) + .expectRedirect('/pypi/frameworkversions/django/django/1.11.json') -t.create('supported django versions (invalid)') +t.create('redirect supported django versions (invalid)') .get('/not-a-package.json') - .expectBadge({ - label: 'django versions', - message: 'package or version not found', - }) + .expectRedirect('/pypi/frameworkversions/django/not-a-package.json') diff --git a/services/pypi/pypi-framework-versions.service.js b/services/pypi/pypi-framework-versions.service.js new file mode 100644 index 0000000000000000000000000000000000000000..1da6edb1e875019cda49726c64faaeaae308c73f --- /dev/null +++ b/services/pypi/pypi-framework-versions.service.js @@ -0,0 +1,103 @@ +import { InvalidResponse } from '../index.js' +import PypiBase from './pypi-base.js' +import { sortPypiVersions, parseClassifiers } from './pypi-helpers.js' + +const frameworkNameMap = { + 'aws-cdk': { + name: 'AWS CDK', + classifier: 'AWS CDK', + }, + django: { + name: 'Django', + classifier: 'Django', + }, + 'django-cms': { + name: 'Django CMS', + classifier: 'Django CMS', + }, + jupyterlab: { + name: 'JupyterLab', + classifier: 'Jupyter :: JupyterLab', + }, + odoo: { + name: 'Odoo', + classifier: 'Odoo', + }, + plone: { + name: 'Plone', + classifier: 'Plone', + }, + wagtail: { + name: 'Wagtail', + classifier: 'Wagtail', + }, + zope: { + name: 'Zope', + classifier: 'Zope', + }, +} + +const documentation = ` +<p> + This service currently support the following Frameworks: <br/> + ${Object.values(frameworkNameMap).map(obj => `<strong>${obj.name}</strong>`)} +</p> +` +export default class PypiFrameworkVersion extends PypiBase { + static category = 'platform-support' + + static route = { + base: 'pypi/frameworkversions', + pattern: `:frameworkName(${Object.keys(frameworkNameMap).join( + '|' + )})/:packageName*`, + } + + static examples = [ + { + title: 'PyPI - Versions from Framework Classifiers', + namedParams: { + frameworkName: 'Plone', + packageName: 'plone.volto', + }, + staticPreview: this.render({ + name: 'Plone', + versions: ['5.2', '6.0'], + }), + keywords: ['python'], + documentation, + }, + ] + + static defaultBadgeData = { label: 'versions' } + + static render({ name, versions }) { + name = name ? name.toLowerCase() : '' + const label = `${name} versions` + return { + label, + message: sortPypiVersions(versions).join(' | '), + color: 'blue', + } + } + + async handle({ frameworkName, packageName }) { + const classifier = frameworkNameMap[frameworkName] + ? frameworkNameMap[frameworkName].classifier + : frameworkName + const name = frameworkNameMap[frameworkName] + ? frameworkNameMap[frameworkName].name + : frameworkName + const regex = new RegExp(`^Framework :: ${classifier} :: ([\\d.]+)$`) + const packageData = await this.fetch({ egg: packageName }) + const versions = parseClassifiers(packageData, regex) + + if (versions.length === 0) { + throw new InvalidResponse({ + prettyMessage: `${name} versions are missing for ${packageName}`, + }) + } + + return this.constructor.render({ name, versions }) + } +} diff --git a/services/pypi/pypi-framework-versions.tester.js b/services/pypi/pypi-framework-versions.tester.js new file mode 100644 index 0000000000000000000000000000000000000000..89d2f9821b7a76d1756cbb325d22a3d285d6e95c --- /dev/null +++ b/services/pypi/pypi-framework-versions.tester.js @@ -0,0 +1,164 @@ +import Joi from 'joi' +import { createServiceTester } from '../tester.js' +export const t = await createServiceTester() + +const isPipeSeparatedFrameworkVersions = Joi.string().regex( + /^([1-9]+(\.[0-9]+)?(?: \| )?)+$/ +) + +t.create('supported django versions (valid, package version in request)') + .get('/django/djangorestframework/3.7.3.json') + .expectBadge({ + label: 'django versions', + message: isPipeSeparatedFrameworkVersions, + }) + +t.create('supported django versions (valid, no package version specified)') + .get('/django/djangorestframework.json') + .expectBadge({ + label: 'django versions', + message: isPipeSeparatedFrameworkVersions, + }) + +t.create('supported django versions (no versions specified)') + .get('/django/django/1.11.json') + .expectBadge({ + label: 'versions', + message: 'Django versions are missing for django/1.11', + }) + +t.create('supported django versions (invalid)') + .get('/django/not-a-package.json') + .expectBadge({ + label: 'versions', + message: 'package or version not found', + }) + +t.create('supported plone versions (valid, package version in request)') + .get('/plone/plone.rest/1.6.2.json') + .expectBadge({ label: 'plone versions', message: '4.3 | 5.0 | 5.1 | 5.2' }) + +t.create('supported plone versions (valid, no package version specified)') + .get('/plone/plone.rest.json') + .expectBadge({ + label: 'plone versions', + message: isPipeSeparatedFrameworkVersions, + }) + +t.create('supported plone versions (invalid)') + .get('/plone/not-a-package.json') + .expectBadge({ + label: 'versions', + message: 'package or version not found', + }) + +t.create('supported zope versions (valid, package version in request)') + .get('/zope/plone/5.2.9.json') + .expectBadge({ label: 'zope versions', message: '4' }) + +t.create('supported zope versions (valid, no package version specified)') + .get('/zope/Plone.json') + .expectBadge({ + label: 'zope versions', + message: isPipeSeparatedFrameworkVersions, + }) + +t.create('supported zope versions (invalid)') + .get('/zope/not-a-package.json') + .expectBadge({ + label: 'versions', + message: 'package or version not found', + }) + +t.create('supported wagtail versions (valid, package version in request)') + .get('/wagtail/wagtail-headless-preview/0.3.0.json') + .expectBadge({ label: 'wagtail versions', message: '2 | 3' }) + +t.create('supported wagtail versions (valid, no package version specified)') + .get('/wagtail/wagtail-headless-preview.json') + .expectBadge({ + label: 'wagtail versions', + message: isPipeSeparatedFrameworkVersions, + }) + +t.create('supported wagtail versions (invalid)') + .get('/wagtail/not-a-package.json') + .expectBadge({ + label: 'versions', + message: 'package or version not found', + }) + +t.create('supported django cms versions (valid, package version in request)') + .get('/django-cms/djangocms-ads/1.1.0.json') + .expectBadge({ + label: 'django cms versions', + message: '3.7 | 3.8 | 3.9 | 3.10', + }) + +t.create('supported django cms versions (valid, no package version specified)') + .get('/django-cms/djangocms-ads.json') + .expectBadge({ + label: 'django cms versions', + message: isPipeSeparatedFrameworkVersions, + }) + +t.create('supported django cms versions (invalid)') + .get('/django-cms/not-a-package.json') + .expectBadge({ + label: 'versions', + message: 'package or version not found', + }) + +t.create('supported odoo versions (valid, package version in request)') + .get('/odoo/odoo-addon-sale-tier-validation/15.0.1.0.0.6.json') + .expectBadge({ label: 'odoo versions', message: '15.0' }) + +t.create('supported odoo versions (valid, no package version specified)') + .get('/odoo/odoo-addon-sale-tier-validation.json') + .expectBadge({ + label: 'odoo versions', + message: isPipeSeparatedFrameworkVersions, + }) + +t.create('supported odoo versions (invalid)') + .get('/odoo/not-a-package.json') + .expectBadge({ + label: 'versions', + message: 'package or version not found', + }) + +t.create('supported aws cdk versions (valid, package version in request)') + .get('/aws-cdk/aws-cdk.aws-glue-alpha/2.34.0a0.json') + .expectBadge({ label: 'aws cdk versions', message: '2' }) + +t.create('supported aws cdk versions (valid, no package version specified)') + .get('/aws-cdk/aws-cdk.aws-glue-alpha.json') + .expectBadge({ + label: 'aws cdk versions', + message: isPipeSeparatedFrameworkVersions, + }) + +t.create('supported aws cdk versions (invalid)') + .get('/aws-cdk/not-a-package.json') + .expectBadge({ + label: 'versions', + message: 'package or version not found', + }) + +t.create('supported jupyterlab versions (valid, package version in request)') + .get('/jupyterlab/structured-text/0.0.2.json') + .expectBadge({ label: 'jupyterlab versions', message: '3' }) + +t.create('supported jupyterlab versions (valid, no package version specified)') + .get('/jupyterlab/structured-text.json') + .expectBadge({ + label: 'jupyterlab versions', + message: isPipeSeparatedFrameworkVersions, + }) + +t.create('supported jupyterlab versions (invalid)') + .get('/jupyterlab/not-a-package.json') + .expectBadge({ + label: 'versions', + message: 'package or version not found', + }) diff --git a/services/pypi/pypi-helpers.js b/services/pypi/pypi-helpers.js index 0e16534d8bd89a39ccd4aec99b5a0e48a742c79b..706955b4f969b28eac1afe9147e3ceec4765780a 100644 --- a/services/pypi/pypi-helpers.js +++ b/services/pypi/pypi-helpers.js @@ -6,7 +6,7 @@ our own functions to parse and sort django versions */ -function parseDjangoVersionString(str) { +function parsePypiVersionString(str) { if (typeof str !== 'string') { return false } @@ -20,18 +20,12 @@ function parseDjangoVersionString(str) { } // Sort an array of django versions low to high. -function sortDjangoVersions(versions) { +function sortPypiVersions(versions) { return versions.sort((a, b) => { - if ( - parseDjangoVersionString(a).major === parseDjangoVersionString(b).major - ) { - return ( - parseDjangoVersionString(a).minor - parseDjangoVersionString(b).minor - ) + if (parsePypiVersionString(a).major === parsePypiVersionString(b).major) { + return parsePypiVersionString(a).minor - parsePypiVersionString(b).minor } else { - return ( - parseDjangoVersionString(a).major - parseDjangoVersionString(b).major - ) + return parsePypiVersionString(a).major - parsePypiVersionString(b).major } }) } @@ -101,8 +95,8 @@ function getPackageFormats(packageData) { export { parseClassifiers, - parseDjangoVersionString, - sortDjangoVersions, + parsePypiVersionString, + sortPypiVersions, getLicenses, getPackageFormats, } diff --git a/services/pypi/pypi-helpers.spec.js b/services/pypi/pypi-helpers.spec.js index d5a54dd5f505f0d1fd6c78d101fba93683ce20ee..17e42b3b4fd5ad073324251bca9f2e462588e60a 100644 --- a/services/pypi/pypi-helpers.spec.js +++ b/services/pypi/pypi-helpers.spec.js @@ -1,8 +1,8 @@ import { test, given, forCases } from 'sazerac' import { parseClassifiers, - parseDjangoVersionString, - sortDjangoVersions, + parsePypiVersionString, + sortPypiVersions, getLicenses, getPackageFormats, } from './pypi-helpers.js' @@ -60,7 +60,7 @@ describe('PyPI helpers', function () { given(classifiersFixture, /^(?!.*)*$/).expect([]) }) - test(parseDjangoVersionString, function () { + test(parsePypiVersionString, function () { given('1').expect({ major: 1, minor: 0 }) given('1.0').expect({ major: 1, minor: 0 }) given('7.2').expect({ major: 7, minor: 2 }) @@ -69,7 +69,7 @@ describe('PyPI helpers', function () { given('foo').expect({ major: 0, minor: 0 }) }) - test(sortDjangoVersions, function () { + test(sortPypiVersions, function () { // Each of these includes a different variant: 2.0, 2, and 2.0rc1. given(['2.0', '1.9', '10', '1.11', '2.1', '2.11']).expect([ '1.9',