Rewrite [pypi]; affects [npm] (#1922)
This commit is contained in:
+3
-1
@@ -7,6 +7,7 @@ const {
|
||||
InvalidResponse,
|
||||
Inaccessible,
|
||||
InvalidParameter,
|
||||
Deprecated,
|
||||
} = require('./errors')
|
||||
const queryString = require('query-string')
|
||||
const {
|
||||
@@ -187,7 +188,8 @@ class BaseService {
|
||||
}
|
||||
} else if (
|
||||
error instanceof InvalidResponse ||
|
||||
error instanceof Inaccessible
|
||||
error instanceof Inaccessible ||
|
||||
error instanceof Deprecated
|
||||
) {
|
||||
trace.logTrace('outbound', emojic.noGoodWoman, 'Handled error', error)
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
'use strict'
|
||||
|
||||
const BaseService = require('./base')
|
||||
const { Deprecated } = require('./errors')
|
||||
|
||||
// Only `url` is required.
|
||||
function deprecatedService({ url, label, category, examples = [] }) {
|
||||
return class DeprecatedService extends BaseService {
|
||||
static get category() {
|
||||
return category
|
||||
}
|
||||
|
||||
static get url() {
|
||||
return url
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return { label }
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return examples
|
||||
}
|
||||
|
||||
async handle() {
|
||||
throw new Deprecated()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = deprecatedService
|
||||
@@ -83,9 +83,24 @@ class InvalidParameter extends ShieldsRuntimeError {
|
||||
}
|
||||
}
|
||||
|
||||
class Deprecated extends ShieldsRuntimeError {
|
||||
get name() {
|
||||
return 'Deprecated'
|
||||
}
|
||||
get defaultPrettyMessage() {
|
||||
return 'no longer available'
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
const message = 'Deprecated'
|
||||
super(props, message)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
NotFound,
|
||||
InvalidResponse,
|
||||
Inaccessible,
|
||||
InvalidParameter,
|
||||
Deprecated,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const { licenseToColor } = require('../../lib/licenses')
|
||||
const { renderLicenseBadge } = require('../../lib/licenses')
|
||||
const { toArray } = require('../../lib/badge-data')
|
||||
const NpmBase = require('./npm-base')
|
||||
|
||||
@@ -9,10 +9,6 @@ module.exports = class NpmLicense extends NpmBase {
|
||||
return 'license'
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return { label: 'license' }
|
||||
}
|
||||
|
||||
static get url() {
|
||||
return this.buildUrl('npm/l', { withTag: false })
|
||||
}
|
||||
@@ -36,16 +32,7 @@ module.exports = class NpmLicense extends NpmBase {
|
||||
}
|
||||
|
||||
static render({ licenses }) {
|
||||
if (licenses.length === 0) {
|
||||
return { message: 'missing', color: 'red' }
|
||||
}
|
||||
|
||||
return {
|
||||
message: licenses.join(', '),
|
||||
// TODO This does not provide a color when more than one license is
|
||||
// present. Probably that should be fixed.
|
||||
color: licenseToColor(licenses),
|
||||
}
|
||||
return renderLicenseBadge({ licenses })
|
||||
}
|
||||
|
||||
async handle(namedParams, queryParams) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { addv } = require('../../lib/text-formatters')
|
||||
const { version: versionColor } = require('../../lib/color-formatters')
|
||||
const { renderVersionBadge } = require('../../lib/version')
|
||||
const { NotFound } = require('../errors')
|
||||
const NpmBase = require('./npm-base')
|
||||
|
||||
@@ -66,11 +65,12 @@ module.exports = class NpmVersion extends NpmBase {
|
||||
}
|
||||
|
||||
static render({ tag, version }) {
|
||||
return {
|
||||
label: tag ? `npm@${tag}` : undefined,
|
||||
message: addv(version),
|
||||
color: versionColor(version),
|
||||
}
|
||||
const { label: defaultLabel } = this.defaultBadgeData
|
||||
return renderVersionBadge({
|
||||
tag,
|
||||
version,
|
||||
defaultLabel,
|
||||
})
|
||||
}
|
||||
|
||||
async handle(namedParams, queryParams) {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const BaseJsonService = require('../base-json')
|
||||
|
||||
const schema = Joi.object({
|
||||
info: Joi.object({
|
||||
version: Joi.string().required(),
|
||||
license: Joi.string().required(),
|
||||
classifiers: Joi.array()
|
||||
.items(Joi.string().required())
|
||||
.required(),
|
||||
}).required(),
|
||||
releases: Joi.object()
|
||||
.pattern(
|
||||
Joi.string(),
|
||||
Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
packagetype: Joi.string().required(),
|
||||
})
|
||||
)
|
||||
.required()
|
||||
)
|
||||
.required(),
|
||||
}).required()
|
||||
|
||||
module.exports = class PypiBase extends BaseJsonService {
|
||||
static buildUrl(base) {
|
||||
return {
|
||||
base,
|
||||
format: '(.*)',
|
||||
capture: ['egg'],
|
||||
}
|
||||
}
|
||||
|
||||
async fetch({ egg }) {
|
||||
return this._requestJson({
|
||||
schema,
|
||||
url: `https://pypi.org/pypi/${egg}/json`,
|
||||
errorMessages: { 404: 'package or version not found' },
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
'use strict'
|
||||
|
||||
const PypiBase = require('./pypi-base')
|
||||
const { sortDjangoVersions, parseClassifiers } = require('./pypi-helpers')
|
||||
|
||||
module.exports = class PypiDjangoVersions extends PypiBase {
|
||||
static get category() {
|
||||
return 'platform-support'
|
||||
}
|
||||
|
||||
static get url() {
|
||||
return this.buildUrl('pypi/djversions')
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return { label: 'django versions' }
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'PyPI - Django Version',
|
||||
previewUrl: 'djangorestframework',
|
||||
keywords: ['python', 'django'],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
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 })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
'use strict'
|
||||
|
||||
const deprecatedService = require('../deprecated-service')
|
||||
const PypiBase = require('./pypi-base')
|
||||
|
||||
// https://github.com/badges/shields/issues/716
|
||||
module.exports = ['pypi/dm', 'pypi/dw', 'pypi/dd'].map(base =>
|
||||
deprecatedService({
|
||||
category: 'downloads',
|
||||
url: PypiBase.buildUrl(base),
|
||||
})
|
||||
)
|
||||
@@ -0,0 +1,53 @@
|
||||
'use strict'
|
||||
|
||||
const PypiBase = require('./pypi-base')
|
||||
const { getPackageFormats } = require('./pypi-helpers')
|
||||
|
||||
module.exports = class PypiFormat extends PypiBase {
|
||||
static get category() {
|
||||
return 'other'
|
||||
}
|
||||
|
||||
static get url() {
|
||||
return this.buildUrl('pypi/format')
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return { label: 'format' }
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'PyPI - Format',
|
||||
previewUrl: 'Django',
|
||||
keywords: ['python'],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static render({ hasWheel, hasEgg }) {
|
||||
if (hasWheel) {
|
||||
return {
|
||||
message: 'wheel',
|
||||
color: 'brightgreen',
|
||||
}
|
||||
} else if (hasEgg) {
|
||||
return {
|
||||
message: 'egg',
|
||||
color: 'red',
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
message: 'source',
|
||||
color: 'yellow',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ egg }) {
|
||||
const packageData = await this.fetch({ egg })
|
||||
const { hasWheel, hasEgg } = getPackageFormats(packageData)
|
||||
return this.constructor.render({ hasWheel, hasEgg })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
'use strict'
|
||||
|
||||
/*
|
||||
Django versions will be specified in the form major.minor
|
||||
trying to sort with `semver.compare` will throw e.g:
|
||||
TypeError: Invalid Version: 1.11
|
||||
because no patch release is specified, so we will define
|
||||
our own functions to parse and sort django versions
|
||||
*/
|
||||
|
||||
const parseDjangoVersionString = function(str) {
|
||||
if (typeof str !== 'string') {
|
||||
return false
|
||||
}
|
||||
const x = str.split('.')
|
||||
const maj = parseInt(x[0]) || 0
|
||||
const min = parseInt(x[1]) || 0
|
||||
return {
|
||||
major: maj,
|
||||
minor: min,
|
||||
}
|
||||
}
|
||||
|
||||
// sort an array of django versions low to high
|
||||
const sortDjangoVersions = function(versions) {
|
||||
return versions.sort((a, b) => {
|
||||
if (
|
||||
parseDjangoVersionString(a).major === parseDjangoVersionString(b).major
|
||||
) {
|
||||
return (
|
||||
parseDjangoVersionString(a).minor - parseDjangoVersionString(b).minor
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
parseDjangoVersionString(a).major - parseDjangoVersionString(b).major
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// extract classifiers from a pypi json response based on a regex
|
||||
const parseClassifiers = function(parsedData, pattern) {
|
||||
const results = []
|
||||
for (let i = 0; i < parsedData.info.classifiers.length; i++) {
|
||||
const matched = pattern.exec(parsedData.info.classifiers[i])
|
||||
if (matched && matched[1]) {
|
||||
results.push(matched[1].toLowerCase())
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
function getPackageFormats(packageData) {
|
||||
const {
|
||||
info: { version },
|
||||
releases,
|
||||
} = packageData
|
||||
const releasesForVersion = releases[version]
|
||||
return {
|
||||
hasWheel: releasesForVersion.some(({ packagetype }) =>
|
||||
['wheel', 'bdist_wheel'].includes(packagetype)
|
||||
),
|
||||
hasEgg: releasesForVersion.some(({ packagetype }) =>
|
||||
['egg', 'bdist_egg'].includes(packagetype)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseClassifiers,
|
||||
parseDjangoVersionString,
|
||||
sortDjangoVersions,
|
||||
getPackageFormats,
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
'use strict'
|
||||
|
||||
const { test, given } = require('sazerac')
|
||||
const {
|
||||
parseClassifiers,
|
||||
parseDjangoVersionString,
|
||||
sortDjangoVersions,
|
||||
getPackageFormats,
|
||||
} = require('./pypi-helpers.js')
|
||||
|
||||
const classifiersFixture = {
|
||||
info: {
|
||||
classifiers: [
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Web Environment',
|
||||
'Framework :: Django',
|
||||
'Framework :: Django :: 1.10',
|
||||
'Framework :: Django :: 1.11',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Operating System :: OS Independent',
|
||||
'Natural Language :: English',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Topic :: Internet :: WWW/HTTP',
|
||||
'Programming Language :: Python :: Implementation :: CPython',
|
||||
'Programming Language :: Python :: Implementation :: PyPy',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
describe('PyPI helpers', function() {
|
||||
test(parseClassifiers, function() {
|
||||
given(
|
||||
classifiersFixture,
|
||||
/^Programming Language :: Python :: ([\d.]+)$/
|
||||
).expect(['2', '2.7', '3', '3.4', '3.5', '3.6'])
|
||||
|
||||
given(classifiersFixture, /^Framework :: Django :: ([\d.]+)$/).expect([
|
||||
'1.10',
|
||||
'1.11',
|
||||
])
|
||||
|
||||
given(
|
||||
classifiersFixture,
|
||||
/^Programming Language :: Python :: Implementation :: (\S+)$/
|
||||
).expect(['cpython', 'pypy'])
|
||||
|
||||
// regex that matches everything
|
||||
given(classifiersFixture, /^([\S\s+]+)$/).expect(
|
||||
classifiersFixture.info.classifiers.map(e => e.toLowerCase())
|
||||
)
|
||||
|
||||
// regex that matches nothing
|
||||
given(classifiersFixture, /^(?!.*)*$/).expect([])
|
||||
})
|
||||
|
||||
test(parseDjangoVersionString, function() {
|
||||
given('1').expect({ major: 1, minor: 0 })
|
||||
given('1.0').expect({ major: 1, minor: 0 })
|
||||
given('7.2').expect({ major: 7, minor: 2 })
|
||||
given('7.2derpderp').expect({ major: 7, minor: 2 })
|
||||
given('7.2.9.5.8.3').expect({ major: 7, minor: 2 })
|
||||
given('foo').expect({ major: 0, minor: 0 })
|
||||
})
|
||||
|
||||
test(sortDjangoVersions, 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',
|
||||
'1.11',
|
||||
'2.0',
|
||||
'2.1',
|
||||
'2.11',
|
||||
'10',
|
||||
])
|
||||
|
||||
given(['2', '1.9', '10', '1.11', '2.1', '2.11']).expect([
|
||||
'1.9',
|
||||
'1.11',
|
||||
'2',
|
||||
'2.1',
|
||||
'2.11',
|
||||
'10',
|
||||
])
|
||||
|
||||
given(['2.0rc1', '10', '1.9', '1.11', '2.1', '2.11']).expect([
|
||||
'1.9',
|
||||
'1.11',
|
||||
'2.0rc1',
|
||||
'2.1',
|
||||
'2.11',
|
||||
'10',
|
||||
])
|
||||
})
|
||||
|
||||
test(getPackageFormats, () => {
|
||||
given({
|
||||
info: { version: '2.19.1' },
|
||||
releases: {
|
||||
'1.0.4': [{ packagetype: 'sdist' }],
|
||||
'2.19.1': [{ packagetype: 'bdist_wheel' }, { packagetype: 'sdist' }],
|
||||
},
|
||||
}).expect({ hasWheel: true, hasEgg: false })
|
||||
given({
|
||||
info: { version: '1.0.4' },
|
||||
releases: {
|
||||
'1.0.4': [{ packagetype: 'sdist' }],
|
||||
'2.19.1': [{ packagetype: 'bdist_wheel' }, { packagetype: 'sdist' }],
|
||||
},
|
||||
}).expect({ hasWheel: false, hasEgg: false })
|
||||
given({
|
||||
info: { version: '0.8.2' },
|
||||
releases: {
|
||||
'0.8': [{ packagetype: 'sdist' }],
|
||||
'0.8.1': [
|
||||
{ packagetype: 'bdist_egg' },
|
||||
{ packagetype: 'bdist_egg' },
|
||||
{ packagetype: 'sdist' },
|
||||
],
|
||||
'0.8.2': [
|
||||
{ packagetype: 'bdist_egg' },
|
||||
{ packagetype: 'bdist_egg' },
|
||||
{ packagetype: 'sdist' },
|
||||
],
|
||||
},
|
||||
}).expect({ hasWheel: false, hasEgg: true })
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,50 @@
|
||||
'use strict'
|
||||
|
||||
const PypiBase = require('./pypi-base')
|
||||
const { parseClassifiers } = require('./pypi-helpers')
|
||||
|
||||
module.exports = class PypiImplementation extends PypiBase {
|
||||
static get category() {
|
||||
return 'other'
|
||||
}
|
||||
|
||||
static get url() {
|
||||
return this.buildUrl('pypi/implementation')
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return { label: 'implementation' }
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'PyPI - Implementation',
|
||||
previewUrl: 'Django',
|
||||
keywords: ['python'],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static render({ implementations }) {
|
||||
return {
|
||||
message: implementations.sort().join(' | '),
|
||||
color: 'blue',
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ egg }) {
|
||||
const packageData = await this.fetch({ egg })
|
||||
|
||||
let implementations = parseClassifiers(
|
||||
packageData,
|
||||
/^Programming Language :: Python :: Implementation :: (\S+)$/
|
||||
)
|
||||
if (implementations.length === 0) {
|
||||
// Assume CPython.
|
||||
implementations = ['cpython']
|
||||
}
|
||||
|
||||
return this.constructor.render({ implementations })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
'use strict'
|
||||
|
||||
const { renderLicenseBadge } = require('../../lib/licenses')
|
||||
const PypiBase = require('./pypi-base')
|
||||
|
||||
module.exports = class PypiLicense extends PypiBase {
|
||||
static get category() {
|
||||
return 'license'
|
||||
}
|
||||
|
||||
static get url() {
|
||||
return this.buildUrl('pypi/l')
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'PyPI - License',
|
||||
previewUrl: 'Django',
|
||||
keywords: ['python'],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static render({ license }) {
|
||||
return renderLicenseBadge({ license })
|
||||
}
|
||||
|
||||
async handle({ egg }) {
|
||||
const {
|
||||
info: { license },
|
||||
} = await this.fetch({ egg })
|
||||
return this.constructor.render({ license })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
'use strict'
|
||||
|
||||
const PypiBase = require('./pypi-base')
|
||||
const { parseClassifiers } = require('./pypi-helpers')
|
||||
|
||||
module.exports = class PypiPythonVersions extends PypiBase {
|
||||
static get category() {
|
||||
return 'platform-support'
|
||||
}
|
||||
|
||||
static get url() {
|
||||
return this.buildUrl('pypi/pyversions')
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return { label: 'python' }
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'PyPI - Python Version',
|
||||
previewUrl: 'Django',
|
||||
keywords: ['python'],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static render({ versions }) {
|
||||
const versionSet = new Set(versions)
|
||||
// We only show v2 if eg. v2.4 does not appear.
|
||||
// See https://github.com/badges/shields/pull/489 for more.
|
||||
;['2', '3'].forEach(majorVersion => {
|
||||
if (Array.from(versions).some(v => v.startsWith(`${majorVersion}.`))) {
|
||||
versionSet.delete(majorVersion)
|
||||
}
|
||||
})
|
||||
if (versionSet.size) {
|
||||
return {
|
||||
message: Array.from(versionSet)
|
||||
.sort()
|
||||
.join(' | '),
|
||||
color: 'blue',
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
message: 'missing',
|
||||
color: 'red',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ egg }) {
|
||||
const packageData = await this.fetch({ egg })
|
||||
|
||||
const versions = parseClassifiers(
|
||||
packageData,
|
||||
/^Programming Language :: Python :: ([\d.]+)$/
|
||||
)
|
||||
|
||||
return this.constructor.render({ versions })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
'use strict'
|
||||
|
||||
const PypiBase = require('./pypi-base')
|
||||
const { parseClassifiers } = require('./pypi-helpers')
|
||||
|
||||
module.exports = class PypiStatus extends PypiBase {
|
||||
static get category() {
|
||||
return 'other'
|
||||
}
|
||||
|
||||
static get url() {
|
||||
return this.buildUrl('pypi/status')
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return { label: 'status' }
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'PyPI - Status',
|
||||
previewUrl: 'Django',
|
||||
keywords: ['python'],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static render({ status = '' }) {
|
||||
status = status.toLowerCase()
|
||||
|
||||
const color = {
|
||||
planning: 'red',
|
||||
'pre-alpha': 'red',
|
||||
alpha: 'red',
|
||||
beta: 'yellow',
|
||||
stable: 'brightgreen',
|
||||
mature: 'brightgreen',
|
||||
inactive: 'red',
|
||||
}[status]
|
||||
|
||||
return {
|
||||
message: status,
|
||||
color,
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ egg }) {
|
||||
const packageData = await this.fetch({ egg })
|
||||
|
||||
// Possible statuses:
|
||||
// - Development Status :: 1 - Planning
|
||||
// - Development Status :: 2 - Pre-Alpha
|
||||
// - Development Status :: 3 - Alpha
|
||||
// - Development Status :: 4 - Beta
|
||||
// - Development Status :: 5 - Production/Stable
|
||||
// - Development Status :: 6 - Mature
|
||||
// - Development Status :: 7 - Inactive
|
||||
// https://pypi.org/pypi?%3Aaction=list_classifiers
|
||||
const status = parseClassifiers(
|
||||
packageData,
|
||||
/^Development Status :: (\d - \S+)$/
|
||||
)
|
||||
.sort()
|
||||
.map(classifier => classifier.split(' - ').pop())
|
||||
.map(classifier => classifier.replace(/production\/stable/i, 'stable'))
|
||||
.pop()
|
||||
|
||||
return this.constructor.render({ status })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
'use strict'
|
||||
|
||||
const { renderVersionBadge } = require('../../lib/version')
|
||||
const PypiBase = require('./pypi-base')
|
||||
|
||||
module.exports = class PypiVersion extends PypiBase {
|
||||
static get category() {
|
||||
return 'version'
|
||||
}
|
||||
|
||||
static get url() {
|
||||
return this.buildUrl('pypi/v')
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return { label: 'pypi' }
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'PyPI',
|
||||
previewUrl: 'nine',
|
||||
keywords: ['python'],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static render({ version }) {
|
||||
return renderVersionBadge({ version })
|
||||
}
|
||||
|
||||
async handle({ egg }) {
|
||||
const {
|
||||
info: { version },
|
||||
} = await this.fetch({ egg })
|
||||
return this.constructor.render({ version })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
'use strict'
|
||||
|
||||
const PypiBase = require('./pypi-base')
|
||||
const { getPackageFormats } = require('./pypi-helpers')
|
||||
|
||||
module.exports = class PypiWheel extends PypiBase {
|
||||
static get category() {
|
||||
return 'other'
|
||||
}
|
||||
|
||||
static get url() {
|
||||
return this.buildUrl('pypi/wheel')
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return { label: 'wheel' }
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'PyPI - Wheel',
|
||||
previewUrl: 'Django',
|
||||
keywords: ['python'],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static render({ hasWheel }) {
|
||||
if (hasWheel) {
|
||||
return {
|
||||
message: 'yes',
|
||||
color: 'brightgreen',
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
message: 'no',
|
||||
color: 'red',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ egg }) {
|
||||
const packageData = await this.fetch({ egg })
|
||||
const { hasWheel } = getPackageFormats(packageData)
|
||||
return this.constructor.render({ hasWheel })
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,11 @@ const { isSemver } = require('../test-validators')
|
||||
|
||||
const isPsycopg2Version = Joi.string().regex(/^v([0-9][.]?)+$/)
|
||||
|
||||
// These regexes are the same, but defined separately for clarity.
|
||||
const isCommaSeperatedPythonVersions = Joi.string().regex(
|
||||
/^([0-9]+.[0-9]+[,]?[ ]?)+$/
|
||||
)
|
||||
const isCommaSeperatedDjangoVersions = Joi.string().regex(
|
||||
/^([0-9]+.[0-9]+[,]?[ ]?)+$/
|
||||
// These regexes are the same, but declared separately for clarity.
|
||||
const isPipeSeparatedPythonVersions = Joi.string().regex(
|
||||
/^([0-9]+.[0-9]+(?: \| )?)+$/
|
||||
)
|
||||
const isPipeSeparatedDjangoVersions = isPipeSeparatedPythonVersions
|
||||
|
||||
const t = new ServiceTester({ id: 'pypi', title: 'PyPi badges' })
|
||||
module.exports = t
|
||||
@@ -39,15 +37,15 @@ t.create('monthly downloads (expected failure)')
|
||||
|
||||
t.create('daily downloads (invalid)')
|
||||
.get('/dd/not-a-package.json')
|
||||
.expectJSON({ name: 'pypi', value: 'invalid' })
|
||||
.expectJSON({ name: 'downloads', value: 'no longer available' })
|
||||
|
||||
t.create('weekly downloads (invalid)')
|
||||
.get('/dw/not-a-package.json')
|
||||
.expectJSON({ name: 'pypi', value: 'invalid' })
|
||||
.expectJSON({ name: 'downloads', value: 'no longer available' })
|
||||
|
||||
t.create('monthly downloads (invalid)')
|
||||
.get('/dm/not-a-package.json')
|
||||
.expectJSON({ name: 'pypi', value: 'invalid' })
|
||||
.expectJSON({ name: 'downloads', value: 'no longer available' })
|
||||
|
||||
/*
|
||||
tests for version endpoint
|
||||
@@ -84,7 +82,7 @@ t.create('version (not semver)')
|
||||
|
||||
t.create('version (invalid)')
|
||||
.get('/v/not-a-package.json')
|
||||
.expectJSON({ name: 'pypi', value: 'invalid' })
|
||||
.expectJSON({ name: 'pypi', value: 'package or version not found' })
|
||||
|
||||
// tests for license endpoint
|
||||
|
||||
@@ -98,7 +96,7 @@ t.create('license (valid, no package version specified)')
|
||||
|
||||
t.create('license (invalid)')
|
||||
.get('/l/not-a-package.json')
|
||||
.expectJSON({ name: 'pypi', value: 'invalid' })
|
||||
.expectJSON({ name: 'license', value: 'package or version not found' })
|
||||
|
||||
// tests for wheel endpoint
|
||||
|
||||
@@ -116,7 +114,7 @@ t.create('wheel (no wheel)')
|
||||
|
||||
t.create('wheel (invalid)')
|
||||
.get('/wheel/not-a-package.json')
|
||||
.expectJSON({ name: 'pypi', value: 'invalid' })
|
||||
.expectJSON({ name: 'wheel', value: 'package or version not found' })
|
||||
|
||||
// tests for format endpoint
|
||||
|
||||
@@ -138,7 +136,7 @@ t.create('format (egg)')
|
||||
|
||||
t.create('format (invalid)')
|
||||
.get('/format/not-a-package.json')
|
||||
.expectJSON({ name: 'pypi', value: 'invalid' })
|
||||
.expectJSON({ name: 'format', value: 'package or version not found' })
|
||||
|
||||
// tests for pyversions endpoint
|
||||
|
||||
@@ -147,7 +145,7 @@ t.create('python versions (valid, package version in request)')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'python',
|
||||
value: isCommaSeperatedPythonVersions,
|
||||
value: isPipeSeparatedPythonVersions,
|
||||
})
|
||||
)
|
||||
|
||||
@@ -156,17 +154,17 @@ t.create('python versions (valid, no package version specified)')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'python',
|
||||
value: isCommaSeperatedPythonVersions,
|
||||
value: isPipeSeparatedPythonVersions,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('python versions (no versions specified)')
|
||||
.get('/pyversions/pyshp/1.2.12.json')
|
||||
.expectJSON({ name: 'python', value: 'not found' })
|
||||
.expectJSON({ name: 'python', value: 'missing' })
|
||||
|
||||
t.create('python versions (invalid)')
|
||||
.get('/pyversions/not-a-package.json')
|
||||
.expectJSON({ name: 'pypi', value: 'invalid' })
|
||||
.expectJSON({ name: 'python', value: 'package or version not found' })
|
||||
|
||||
// tests for django versions endpoint
|
||||
|
||||
@@ -175,7 +173,7 @@ t.create('supported django versions (valid, package version in request)')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'django versions',
|
||||
value: isCommaSeperatedDjangoVersions,
|
||||
value: isPipeSeparatedDjangoVersions,
|
||||
})
|
||||
)
|
||||
|
||||
@@ -184,23 +182,26 @@ t.create('supported django versions (valid, no package version specified)')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'django versions',
|
||||
value: isCommaSeperatedDjangoVersions,
|
||||
value: isPipeSeparatedDjangoVersions,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('supported django versions (no versions specified)')
|
||||
.get('/djversions/django/1.11.json')
|
||||
.expectJSON({ name: 'django versions', value: 'not found' })
|
||||
.expectJSON({ name: 'django versions', value: 'missing' })
|
||||
|
||||
t.create('supported django versions (invalid)')
|
||||
.get('/djversions/not-a-package.json')
|
||||
.expectJSON({ name: 'pypi', value: 'invalid' })
|
||||
.expectJSON({
|
||||
name: 'django versions',
|
||||
value: 'package or version not found',
|
||||
})
|
||||
|
||||
// tests for implementation endpoint
|
||||
|
||||
t.create('implementation (valid, package version in request)')
|
||||
.get('/implementation/beehive/1.0.json')
|
||||
.expectJSON({ name: 'implementation', value: 'cpython, jython, pypy' })
|
||||
.expectJSON({ name: 'implementation', value: 'cpython | jython | pypy' })
|
||||
|
||||
t.create('implementation (valid, no package version specified)')
|
||||
.get('/implementation/numpy.json')
|
||||
@@ -212,7 +213,7 @@ t.create('implementation (not specified)')
|
||||
|
||||
t.create('implementation (invalid)')
|
||||
.get('/implementation/not-a-package.json')
|
||||
.expectJSON({ name: 'pypi', value: 'invalid' })
|
||||
.expectJSON({ name: 'implementation', value: 'package or version not found' })
|
||||
|
||||
// tests for status endpoint
|
||||
|
||||
@@ -230,4 +231,4 @@ t.create('status (valid, beta)')
|
||||
|
||||
t.create('status (invalid)')
|
||||
.get('/status/not-a-package.json')
|
||||
.expectJSON({ name: 'pypi', value: 'invalid' })
|
||||
.expectJSON({ name: 'status', value: 'package or version not found' })
|
||||
|
||||
Reference in New Issue
Block a user