Skip to content

Commit 05ec3c9

Browse files
committed
.
0 parents  commit 05ec3c9

10 files changed

+756
-0
lines changed

.editorconfig

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 2
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true

.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.DS_Store
2+
*.log
3+
.nyc_output/
4+
coverage/
5+
node_modules/
6+
bcp-47-match.js
7+
bcp-47-match.min.js
8+
yarn.lock

.npmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false

.prettierignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
coverage/
2+
bcp-47-match.js
3+
bcp-47-match.min.js

.travis.yml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
language: node_js
2+
node_js:
3+
- lts/boron
4+
- node
5+
after_success: bash <(curl -s https://codecov.io/bash)
6+
deploy:
7+
provider: releases
8+
skip_cleanup: true
9+
api_key:
10+
secure: QeQfflMa+5jDcU1rTUpFjbcfuYBX6ViBVnP3rKp0qafSJmggG6fIAC1ancE2eUecU2FhjAPN1VKmOLa1+J+Lga+F0X1Jy8FgPq4IBDlJ9QvlMkDxfdeoe7LR0RfJcp3IEfJTn/7NJ7hLqXdZNxhvcoKplS2nAtMYWx+uPJWVymdriAfFE551WF+3KTRHeUeQFqnHEnXNjx8thkxbMRMKBTe/U0svo3nUKLJ1M9WOAhsZrByERvV+nFK/XK3keMMZzIs9hadw7OU1EGtpiqA/HApPWSGd5I2aq3fqxjbwHzrJr2KZG61lJeyVoypRGZgPNKlHXy3xxLKWQ6GD4bySu0dke0dZfxzrJOEd7lx45qkaMsb5uwQdbXjnqnkFc/I6xesTCFdl7SdhfwfyH+mPNVMI2o8nOJ3Ud5Am2QycIfuc6r/E/Cx1PiH9+TwmKjtoD9gW15+9Pzjdg9W0IFWysk8c8jWyVawhwoM3fvTvfTfIJJ7uM92aS8S5ReAQfa6sNtRjAluig+7kwausElckTPchOxQbgW4n3NgBV8dP987U5l9PsUOXaMQ9efO9BVg97BqIKSDU+c5lH5wKirG4GkjPK7THIYZUHdRgMbH3ladNoI8RegVP3FG95ZdxfWsx4JCs2qxUSgtgRw0WR4e48MNIDdtquIUTGMntyM7TUp4=
11+
file:
12+
- "bcp-47-match.js"
13+
- "bcp-47-match.min.js"
14+
on:
15+
tags: true
16+
node: 'node'

LICENSE

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
(The MIT License)
2+
3+
Copyright (c) 2018 Titus Wormer <[email protected]>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining
6+
a copy of this software and associated documentation files (the
7+
'Software'), to deal in the Software without restriction, including
8+
without limitation the rights to use, copy, modify, merge, publish,
9+
distribute, sublicense, and/or sell copies of the Software, and to
10+
permit persons to whom the Software is furnished to do so, subject to
11+
the following conditions:
12+
13+
The above copyright notice and this permission notice shall be
14+
included in all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

index.js

+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
'use strict'
2+
3+
/* eslint-disable no-constant-condition */
4+
5+
// See https://tools.ietf.org/html/rfc4647#section-3.1
6+
// for more information on the algorithms.
7+
8+
var dash = '-'
9+
var asterisk = '*'
10+
11+
exports.basicFilter = factory(basic, true)
12+
exports.extendedFilter = factory(extended, true)
13+
exports.lookup = factory(lookup)
14+
15+
// Basic Filtering (Section 3.3.1) matches a language priority list consisting
16+
// of basic language ranges (Section 2.1) to sets of language tags.
17+
function basic(tag, range) {
18+
tag = lower(tag)
19+
range = lower(range)
20+
return range === asterisk || tag === range || tag.indexOf(range + dash) !== -1
21+
}
22+
23+
// Extended Filtering (Section 3.3.2) matches a language priority list
24+
// consisting of extended language ranges (Section 2.2) to sets of language
25+
// tags.
26+
function extended(tag, range) {
27+
// 3.3.2.1
28+
var tags = lower(tag).split(dash)
29+
var ranges = lower(range).split(dash)
30+
var length = ranges.length
31+
var rangeIndex = -1
32+
var tagIndex = -1
33+
34+
tag = tags[++tagIndex]
35+
range = ranges[++rangeIndex]
36+
37+
// 3.3.2.2
38+
if (range !== asterisk && range !== tag) {
39+
return false
40+
}
41+
42+
tag = tags[++tagIndex]
43+
range = ranges[++rangeIndex]
44+
45+
// 3.3.2.3
46+
while (rangeIndex < length) {
47+
// 3.3.2.3.A
48+
if (range === asterisk) {
49+
range = ranges[++rangeIndex]
50+
continue
51+
}
52+
53+
// 3.3.2.3.B
54+
if (!tag) {
55+
return false
56+
}
57+
58+
// 3.3.2.3.C
59+
if (tag === range) {
60+
tag = tags[++tagIndex]
61+
range = ranges[++rangeIndex]
62+
continue
63+
}
64+
65+
// 3.3.2.3.D
66+
if (tag.length === 1) {
67+
return false
68+
}
69+
70+
// 3.3.2.3.E
71+
tag = tags[++tagIndex]
72+
}
73+
74+
// 3.3.2.4
75+
return true
76+
}
77+
78+
// Lookup (Section 3.4) matches a language priority list consisting of basic
79+
// language ranges to sets of language tags to find the one exact language tag
80+
// that best matches the range.
81+
function lookup(tag, range) {
82+
var pos
83+
84+
tag = lower(tag)
85+
range = lower(range)
86+
87+
while (true) {
88+
if (range === asterisk || tag === range) {
89+
return true
90+
}
91+
92+
pos = range.lastIndexOf(dash)
93+
94+
if (pos === -1) {
95+
return false
96+
}
97+
98+
if (pos > 3 && range.charAt(pos - 2) === dash) {
99+
pos -= 2
100+
}
101+
102+
range = range.substring(0, pos)
103+
}
104+
}
105+
106+
// Factory to perform a filter or a lookup.
107+
// This factory creates a function that accepts a list of tags and a list of
108+
// ranges, and contains logic to exit early for lookups.
109+
// `check` just has to deal with one tag and one range.
110+
// This match function iterates over ranges, and for each range,
111+
// iterates over tags. That way, earlier ranges matching any tag have
112+
// precedence over later ranges.
113+
function factory(check, filter) {
114+
return match
115+
116+
function match(tags, ranges) {
117+
var values = normalize(tags, ranges)
118+
var result = []
119+
var next
120+
var tagIndex
121+
var tagLength
122+
var tag
123+
var rangeIndex
124+
var rangeLength
125+
var range
126+
var matches
127+
128+
tags = values.tags
129+
ranges = values.ranges
130+
rangeLength = ranges.length
131+
rangeIndex = -1
132+
133+
while (++rangeIndex < rangeLength) {
134+
range = ranges[rangeIndex]
135+
136+
// Ignore wildcards in lookup mode.
137+
if (!filter && range === asterisk) {
138+
continue
139+
}
140+
141+
tagLength = tags.length
142+
tagIndex = -1
143+
next = []
144+
145+
while (++tagIndex < tagLength) {
146+
tag = tags[tagIndex]
147+
matches = check(tag, range)
148+
;(matches ? result : next).push(tag)
149+
150+
// Exit if this is a lookup and we have a match.
151+
if (!filter && matches) {
152+
return tag
153+
}
154+
}
155+
156+
tags = next
157+
}
158+
159+
// If this is a filter, return the list. If it’s a lookup, we didn’t find
160+
// a match, so return `undefined`.
161+
return filter ? result : undefined
162+
}
163+
}
164+
165+
// Normalize options.
166+
function normalize(tags, ranges) {
167+
ranges = ranges === undefined || ranges === null ? asterisk : ranges
168+
169+
return {tags: cast(tags, 'tag'), ranges: cast(ranges, 'range')}
170+
}
171+
172+
// Validate tags or ranges, and cast them to arrays.
173+
function cast(values, name) {
174+
var value = values && typeof values === 'string' ? [values] : values
175+
176+
if (!value || typeof value !== 'object' || !('length' in value)) {
177+
throw new Error(
178+
'Invalid ' + name + ' `' + value + '`, expected non-empty string'
179+
)
180+
}
181+
182+
return value
183+
}
184+
185+
function lower(value) {
186+
return value.toLowerCase()
187+
}

package.json

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{
2+
"name": "bcp-47-match",
3+
"version": "0.0.0",
4+
"description": "Match BCP 47 language tags with language ranges per RFC 4647",
5+
"license": "MIT",
6+
"keywords": [
7+
"bcp",
8+
"47",
9+
"bcp47",
10+
"bcp-47",
11+
"language",
12+
"tag",
13+
"match",
14+
"matching",
15+
"check",
16+
"rfc",
17+
"4647"
18+
],
19+
"repository": "wooorm/bcp-47-match",
20+
"bugs": "https://github.com/wooorm/bcp-47-match/issues",
21+
"author": "Titus Wormer <[email protected]> (http://wooorm.com)",
22+
"contributors": [
23+
"Titus Wormer <[email protected]> (http://wooorm.com)"
24+
],
25+
"files": [
26+
"index.js"
27+
],
28+
"dependencies": {},
29+
"devDependencies": {
30+
"browserify": "^16.0.0",
31+
"chalk": "^2.4.1",
32+
"nyc": "^12.0.0",
33+
"prettier": "^1.11.0",
34+
"remark-cli": "^5.0.0",
35+
"remark-preset-wooorm": "^4.0.0",
36+
"tape": "^4.0.0",
37+
"tinyify": "^2.4.3",
38+
"xo": "^0.21.0"
39+
},
40+
"scripts": {
41+
"format": "remark . -qfo && prettier --write '**/*.js' && xo --fix",
42+
"build-bundle": "browserify index.js -s bcp47Match > bcp-47-match.js",
43+
"build-mangle": "browserify index.js -s bcp47Match -p tinyify > bcp-47-match.min.js",
44+
"build": "npm run build-bundle && npm run build-mangle",
45+
"test-api": "node test",
46+
"test-coverage": "nyc --reporter lcov tape test.js",
47+
"test": "npm run format && npm run build && npm run test-coverage"
48+
},
49+
"prettier": {
50+
"tabWidth": 2,
51+
"useTabs": false,
52+
"singleQuote": true,
53+
"bracketSpacing": false,
54+
"semi": false,
55+
"trailingComma": "none"
56+
},
57+
"xo": {
58+
"prettier": true,
59+
"esnext": false
60+
},
61+
"nyc": {
62+
"check-coverage": true,
63+
"lines": 100,
64+
"functions": 100,
65+
"branches": 100
66+
},
67+
"remarkConfig": {
68+
"plugins": [
69+
"preset-wooorm"
70+
]
71+
}
72+
}

0 commit comments

Comments
 (0)