bcp-47-match
Advanced tools
Comparing version 1.0.3 to 2.0.0
269
index.js
@@ -1,132 +0,197 @@ | ||
'use strict' | ||
/** | ||
* See https://tools.ietf.org/html/rfc4647#section-3.1 | ||
* for more information on the algorithms. | ||
*/ | ||
// See https://tools.ietf.org/html/rfc4647#section-3.1 | ||
// for more information on the algorithms. | ||
/** | ||
* @typedef {string} Tag | ||
* @typedef {Array.<Tag>} Tags | ||
* @typedef {string} Range | ||
* @typedef {Array.<Range>} Ranges | ||
* @typedef {function(Tag, Range): boolean} Check | ||
* @typedef {function(Tag|Tags, Range|Ranges=): Tags} Filter | ||
* @typedef {function(Tag|Tags, Range|Ranges=): Tag} Lookup | ||
*/ | ||
exports.basicFilter = factory(basic, true) | ||
exports.extendedFilter = factory(extended, true) | ||
exports.lookup = factory(lookup) | ||
/** | ||
* Factory to perform a filter or a lookup. | ||
* This factory creates a function that accepts a list of tags and a list of | ||
* ranges, and contains logic to exit early for lookups. | ||
* `check` just has to deal with one tag and one range. | ||
* This match function iterates over ranges, and for each range, | ||
* iterates over tags. That way, earlier ranges matching any tag have | ||
* precedence over later ranges. | ||
* | ||
* @type {{ | ||
* (check: Check, filter: true): Filter | ||
* (check: Check, filter?: false): Lookup | ||
* }} | ||
*/ | ||
// prettier-ignore | ||
var factory = ( | ||
/** | ||
* @param {Check} check | ||
* @param {boolean} [filter=false] | ||
*/ | ||
function (check, filter) { | ||
return match | ||
// Basic Filtering (Section 3.3.1) matches a language priority list consisting | ||
// of basic language ranges (Section 2.1) to sets of language tags. | ||
function basic(tag, range) { | ||
return range === '*' || tag === range || tag.indexOf(range + '-') > -1 | ||
} | ||
/** | ||
* @param {Tag|Tags} tags | ||
* @param {Range|Ranges} [ranges='*'] | ||
* @returns {Tag|Tags} | ||
*/ | ||
function match(tags, ranges) { | ||
var left = cast(tags, 'tag') | ||
var right = cast( | ||
ranges === null || ranges === undefined ? '*' : ranges, | ||
'range' | ||
) | ||
/** @type {Tags} */ | ||
var matches = [] | ||
var rightIndex = -1 | ||
/** @type {Range} */ | ||
var range | ||
/** @type {number} */ | ||
var leftIndex | ||
/** @type {Tags} */ | ||
var next | ||
// Extended Filtering (Section 3.3.2) matches a language priority list | ||
// consisting of extended language ranges (Section 2.2) to sets of language | ||
// tags. | ||
function extended(tag, range) { | ||
// 3.3.2.1 | ||
var left = tag.split('-') | ||
var right = range.split('-') | ||
var leftIndex = 0 | ||
var rightIndex = 0 | ||
while (++rightIndex < right.length) { | ||
range = right[rightIndex].toLowerCase() | ||
// 3.3.2.2 | ||
if (right[rightIndex] !== '*' && left[leftIndex] !== right[rightIndex]) { | ||
return false | ||
} | ||
// Ignore wildcards in lookup mode. | ||
if (!filter && range === '*') continue | ||
leftIndex++ | ||
rightIndex++ | ||
leftIndex = -1 | ||
next = [] | ||
// 3.3.2.3 | ||
while (rightIndex < right.length) { | ||
// 3.3.2.3.A | ||
if (right[rightIndex] === '*') { | ||
rightIndex++ | ||
continue | ||
} | ||
while (++leftIndex < left.length) { | ||
if (check(left[leftIndex].toLowerCase(), range)) { | ||
// Exit if this is a lookup and we have a match. | ||
if (!filter) return left[leftIndex] | ||
matches.push(left[leftIndex]) | ||
} else { | ||
next.push(left[leftIndex]) | ||
} | ||
} | ||
// 3.3.2.3.B | ||
if (!left[leftIndex]) return false | ||
left = next | ||
} | ||
// 3.3.2.3.C | ||
if (left[leftIndex] === right[rightIndex]) { | ||
leftIndex++ | ||
rightIndex++ | ||
continue | ||
// If this is a filter, return the list. If it’s a lookup, we didn’t find | ||
// a match, so return `undefined`. | ||
return filter ? matches : undefined | ||
} | ||
} | ||
) | ||
// 3.3.2.3.D | ||
if (left[leftIndex].length === 1) return false | ||
/** | ||
* Basic Filtering (Section 3.3.1) matches a language priority list consisting | ||
* of basic language ranges (Section 2.1) to sets of language tags. | ||
* @param {Tag|Tags} tags | ||
* @param {Range|Ranges} [ranges] | ||
* @returns {Tags} | ||
*/ | ||
export var basicFilter = factory( | ||
/** @type {Check} */ | ||
function (tag, range) { | ||
return range === '*' || tag === range || tag.includes(range + '-') | ||
}, | ||
true | ||
) | ||
// 3.3.2.3.E | ||
leftIndex++ | ||
} | ||
/** | ||
* Extended Filtering (Section 3.3.2) matches a language priority list | ||
* consisting of extended language ranges (Section 2.2) to sets of language | ||
* tags. | ||
* @param {Tag|Tags} tags | ||
* @param {Range|Ranges} [ranges] | ||
* @returns {Tags} | ||
*/ | ||
export var extendedFilter = factory( | ||
/** @type {Check} */ | ||
function (tag, range) { | ||
// 3.3.2.1 | ||
var left = tag.split('-') | ||
var right = range.split('-') | ||
var leftIndex = 0 | ||
var rightIndex = 0 | ||
// 3.3.2.4 | ||
return true | ||
} | ||
// 3.3.2.2 | ||
if (right[rightIndex] !== '*' && left[leftIndex] !== right[rightIndex]) { | ||
return false | ||
} | ||
// Lookup (Section 3.4) matches a language priority list consisting of basic | ||
// language ranges to sets of language tags to find the one exact language tag | ||
// that best matches the range. | ||
function lookup(tag, range) { | ||
var right = range | ||
var index | ||
leftIndex++ | ||
rightIndex++ | ||
/* eslint-disable-next-line no-constant-condition */ | ||
while (true) { | ||
if (right === '*' || tag === right) return true | ||
// 3.3.2.3 | ||
while (rightIndex < right.length) { | ||
// 3.3.2.3.A | ||
if (right[rightIndex] === '*') { | ||
rightIndex++ | ||
continue | ||
} | ||
index = right.lastIndexOf('-') | ||
// 3.3.2.3.B | ||
if (!left[leftIndex]) return false | ||
if (index < 0) return false | ||
// 3.3.2.3.C | ||
if (left[leftIndex] === right[rightIndex]) { | ||
leftIndex++ | ||
rightIndex++ | ||
continue | ||
} | ||
if (right.charAt(index - 2) === '-') index -= 2 | ||
// 3.3.2.3.D | ||
if (left[leftIndex].length === 1) return false | ||
right = right.slice(0, index) | ||
} | ||
} | ||
// 3.3.2.3.E | ||
leftIndex++ | ||
} | ||
// Factory to perform a filter or a lookup. | ||
// This factory creates a function that accepts a list of tags and a list of | ||
// ranges, and contains logic to exit early for lookups. | ||
// `check` just has to deal with one tag and one range. | ||
// This match function iterates over ranges, and for each range, | ||
// iterates over tags. That way, earlier ranges matching any tag have | ||
// precedence over later ranges. | ||
function factory(check, filter) { | ||
return match | ||
// 3.3.2.4 | ||
return true | ||
}, | ||
true | ||
) | ||
function match(tags, ranges) { | ||
var left = cast(tags, 'tag') | ||
var right = cast(ranges == null ? '*' : ranges, 'range') | ||
var matches = [] | ||
var rightIndex = -1 | ||
var range | ||
var leftIndex | ||
var next | ||
/** | ||
* Lookup (Section 3.4) matches a language priority list consisting of basic | ||
* language ranges to sets of language tags to find the one exact language tag | ||
* that best matches the range. | ||
* @param {Tag|Tags} tags | ||
* @param {Range|Ranges} [ranges] | ||
* @returns {Tag} | ||
*/ | ||
export var lookup = factory( | ||
/** @type {Check} */ | ||
function (tag, range) { | ||
var right = range | ||
/** @type {number} */ | ||
var index | ||
while (++rightIndex < right.length) { | ||
range = right[rightIndex].toLowerCase() | ||
/* eslint-disable-next-line no-constant-condition */ | ||
while (true) { | ||
if (right === '*' || tag === right) return true | ||
// Ignore wildcards in lookup mode. | ||
if (!filter && range === '*') continue | ||
index = right.lastIndexOf('-') | ||
leftIndex = -1 | ||
next = [] | ||
if (index < 0) return false | ||
while (++leftIndex < left.length) { | ||
if (check(left[leftIndex].toLowerCase(), range)) { | ||
// Exit if this is a lookup and we have a match. | ||
if (!filter) return left[leftIndex] | ||
matches.push(left[leftIndex]) | ||
} else { | ||
next.push(left[leftIndex]) | ||
} | ||
} | ||
if (right.charAt(index - 2) === '-') index -= 2 | ||
left = next | ||
right = right.slice(0, index) | ||
} | ||
// If this is a filter, return the list. If it’s a lookup, we didn’t find | ||
// a match, so return `undefined`. | ||
return filter ? matches : undefined | ||
} | ||
} | ||
) | ||
// Validate tags or ranges, and cast them to arrays. | ||
/** | ||
* Validate tags or ranges, and cast them to arrays. | ||
* | ||
* @param {string|Array.<string>} values | ||
* @param {string} name | ||
* @returns {Array.<string>} | ||
*/ | ||
function cast(values, name) { | ||
@@ -133,0 +198,0 @@ var value = values && typeof values === 'string' ? [values] : values |
{ | ||
"name": "bcp-47-match", | ||
"version": "1.0.3", | ||
"version": "2.0.0", | ||
"description": "Match BCP 47 language tags with language ranges per RFC 4647", | ||
@@ -22,5 +22,5 @@ "license": "MIT", | ||
"funding": { | ||
"type": "github", | ||
"url": "https://github.com/sponsors/wooorm" | ||
}, | ||
"type": "github", | ||
"url": "https://github.com/sponsors/wooorm" | ||
}, | ||
"author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)", | ||
@@ -30,25 +30,30 @@ "contributors": [ | ||
], | ||
"sideEffects": false, | ||
"type": "module", | ||
"main": "index.js", | ||
"types": "index.d.ts", | ||
"files": [ | ||
"index.d.ts", | ||
"index.js" | ||
], | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"browserify": "^17.0.0", | ||
"@types/tape": "^4.0.0", | ||
"c8": "^7.0.0", | ||
"chalk": "^4.0.0", | ||
"nyc": "^15.0.0", | ||
"prettier": "^2.0.0", | ||
"remark-cli": "^9.0.0", | ||
"remark-preset-wooorm": "^8.0.0", | ||
"rimraf": "^3.0.0", | ||
"tape": "^5.0.0", | ||
"tinyify": "^3.0.0", | ||
"xo": "^0.34.0" | ||
"type-coverage": "^2.0.0", | ||
"typescript": "^4.0.0", | ||
"xo": "^0.38.0" | ||
}, | ||
"scripts": { | ||
"prepack": "npm run build && npm run format", | ||
"build": "rimraf \"*.d.ts\" && tsc && type-coverage", | ||
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix", | ||
"build-bundle": "browserify index.js -s bcp47Match -o bcp-47-match.js", | ||
"build-mangle": "browserify index.js -s bcp47Match -p tinyify -o bcp-47-match.min.js", | ||
"build": "npm run build-bundle && npm run build-mangle", | ||
"test-api": "node test", | ||
"test-coverage": "nyc --reporter lcov tape test.js", | ||
"test": "npm run format && npm run build && npm run test-coverage" | ||
"test-api": "node test.js", | ||
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test.js", | ||
"test": "npm run build && npm run format && npm run test-coverage" | ||
}, | ||
@@ -65,21 +70,8 @@ "prettier": { | ||
"prettier": true, | ||
"esnext": false, | ||
"rules": { | ||
"eqeqeq": [ | ||
"error", | ||
"always", | ||
{ | ||
"null": "ignore" | ||
} | ||
], | ||
"no-eq-null": "off", | ||
"unicorn/prefer-includes": "off" | ||
"import/no-mutable-exports": "off", | ||
"no-var": "off", | ||
"prefer-arrow-callback": "off" | ||
} | ||
}, | ||
"nyc": { | ||
"check-coverage": true, | ||
"lines": 100, | ||
"functions": 100, | ||
"branches": 100 | ||
}, | ||
"remarkConfig": { | ||
@@ -99,3 +91,8 @@ "plugins": [ | ||
] | ||
}, | ||
"typeCoverage": { | ||
"atLeast": 100, | ||
"detail": true, | ||
"strict": true | ||
} | ||
} |
@@ -21,5 +21,5 @@ <!--lint disable no-html--> | ||
* [API](#api) | ||
* [`match.basicFilter(tags[, ranges])`](#matchbasicfiltertags-ranges) | ||
* [`match.extendedFilter(tags[, ranges])`](#matchextendedfiltertags-ranges) | ||
* [`match.lookup(tags, ranges)`](#matchlookuptags-ranges) | ||
* [`basicFilter(tags[, ranges='*'])`](#basicfiltertags-ranges) | ||
* [`extendedFilter(tags[, ranges='*'])`](#extendedfiltertags-ranges) | ||
* [`lookup(tags, ranges)`](#lookuptags-ranges) | ||
* [Related](#related) | ||
@@ -30,2 +30,5 @@ * [License](#license) | ||
This package is ESM only: Node 12+ is needed to use it and it must be `import`ed | ||
instead of `require`d. | ||
[npm][]: | ||
@@ -40,22 +43,18 @@ | ||
```js | ||
var match = require('bcp-47-match') | ||
import {basicFilter, extendedFilter, lookup} from 'bcp-47-match' | ||
var basic = match.basicFilter | ||
var extended = match.extendedFilter | ||
var lookup = match.lookup | ||
var tags = ['en-GB', 'de-CH', 'en', 'de'] | ||
console.log(basic(tags, '*')) // => [ 'en-GB', 'de-CH', 'en', 'de' ] | ||
console.log(basic(tags, 'en')) // => [ 'en-GB', 'en' ] | ||
console.log(basic(tags, 'en-GB')) // => [ 'en-GB' ] | ||
console.log(basic(tags, ['en-GB', 'en'])) // => [ 'en-GB', 'en' ] | ||
console.log(basic(tags, 'jp')) // => [] | ||
console.log(basicFilter(tags, '*')) // => [ 'en-GB', 'de-CH', 'en', 'de' ] | ||
console.log(basicFilter(tags, 'en')) // => [ 'en-GB', 'en' ] | ||
console.log(basicFilter(tags, 'en-GB')) // => [ 'en-GB' ] | ||
console.log(basicFilter(tags, ['en-GB', 'en'])) // => [ 'en-GB', 'en' ] | ||
console.log(basicFilter(tags, 'jp')) // => [] | ||
console.log(extended(tags, '*')) // => [ 'en-GB', 'de-CH', 'en', 'de' ] | ||
console.log(extended(tags, 'en')) // => [ 'en-GB', 'en' ] | ||
console.log(extended(tags, 'en-GB')) // => [ 'en-GB' ] | ||
console.log(extended(tags, '*-GB')) // => [ 'en-GB' ] | ||
console.log(extended(tags, ['en-GB', 'en'])) // => [ 'en-GB', 'en' ] | ||
console.log(extended(tags, 'jp')) // => [] | ||
console.log(extendedFilter(tags, '*')) // => [ 'en-GB', 'de-CH', 'en', 'de' ] | ||
console.log(extendedFilter(tags, 'en')) // => [ 'en-GB', 'en' ] | ||
console.log(extendedFilter(tags, 'en-GB')) // => [ 'en-GB' ] | ||
console.log(extendedFilter(tags, '*-GB')) // => [ 'en-GB' ] | ||
console.log(extendedFilter(tags, ['en-GB', 'en'])) // => [ 'en-GB', 'en' ] | ||
console.log(extendedFilter(tags, 'jp')) // => [] | ||
@@ -71,4 +70,8 @@ console.log(lookup(tags, 'en')) // => 'en' | ||
### `match.basicFilter(tags[, ranges])` | ||
This package exports the following identifiers: `basicFilter`, `extendedFilter`, | ||
`lookup`. | ||
There is no default export. | ||
### `basicFilter(tags[, ranges='*'])` | ||
> [See Basic Filtering spec](https://tools.ietf.org/html/rfc4647#section-3.3.1) | ||
@@ -117,3 +120,3 @@ | ||
### `match.extendedFilter(tags[, ranges])` | ||
### `extendedFilter(tags[, ranges='*'])` | ||
@@ -162,3 +165,3 @@ > [See Extended Filtering spec](https://tools.ietf.org/html/rfc4647#section-3.3.2) | ||
### `match.lookup(tags, ranges)` | ||
### `lookup(tags, ranges)` | ||
@@ -229,5 +232,5 @@ > [See Lookup spec](https://tools.ietf.org/html/rfc4647#section-3.4) | ||
[build-badge]: https://img.shields.io/travis/wooorm/bcp-47-match.svg | ||
[build-badge]: https://github.com/wooorm/bcp-47-match/workflows/main/badge.svg | ||
[build]: https://travis-ci.org/wooorm/bcp-47-match | ||
[build]: https://github.com/wooorm/bcp-47-match/actions | ||
@@ -234,0 +237,0 @@ [coverage-badge]: https://img.shields.io/codecov/c/github/wooorm/bcp-47-match.svg |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
16946
5
204
257
Yes
11
1