This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
371 lines
9.7 KiB
371 lines
9.7 KiB
/** |
|
* Copyright (c) 2013-present, Facebook, Inc. |
|
* |
|
* This source code is licensed under the MIT license found in the |
|
* LICENSE file in the root directory of this source tree. |
|
* |
|
* @providesModule VersionRange |
|
*/ |
|
|
|
'use strict'; |
|
|
|
const invariant = require('./invariant'); |
|
|
|
const componentRegex = /\./; |
|
const orRegex = /\|\|/; |
|
const rangeRegex = /\s+\-\s+/; |
|
const modifierRegex = /^(<=|<|=|>=|~>|~|>|)?\s*(.+)/; |
|
const numericRegex = /^(\d*)(.*)/; |
|
|
|
/** |
|
* Splits input `range` on "||" and returns true if any subrange matches |
|
* `version`. |
|
* |
|
* @param {string} range |
|
* @param {string} version |
|
* @returns {boolean} |
|
*/ |
|
function checkOrExpression(range, version) { |
|
const expressions = range.split(orRegex); |
|
|
|
if (expressions.length > 1) { |
|
return expressions.some(range => VersionRange.contains(range, version)); |
|
} else { |
|
range = expressions[0].trim(); |
|
return checkRangeExpression(range, version); |
|
} |
|
} |
|
|
|
/** |
|
* Splits input `range` on " - " (the surrounding whitespace is required) and |
|
* returns true if version falls between the two operands. |
|
* |
|
* @param {string} range |
|
* @param {string} version |
|
* @returns {boolean} |
|
*/ |
|
function checkRangeExpression(range, version) { |
|
const expressions = range.split(rangeRegex); |
|
|
|
invariant(expressions.length > 0 && expressions.length <= 2, 'the "-" operator expects exactly 2 operands'); |
|
|
|
if (expressions.length === 1) { |
|
return checkSimpleExpression(expressions[0], version); |
|
} else { |
|
const [startVersion, endVersion] = expressions; |
|
invariant(isSimpleVersion(startVersion) && isSimpleVersion(endVersion), 'operands to the "-" operator must be simple (no modifiers)'); |
|
|
|
return checkSimpleExpression('>=' + startVersion, version) && checkSimpleExpression('<=' + endVersion, version); |
|
} |
|
} |
|
|
|
/** |
|
* Checks if `range` matches `version`. `range` should be a "simple" range (ie. |
|
* not a compound range using the " - " or "||" operators). |
|
* |
|
* @param {string} range |
|
* @param {string} version |
|
* @returns {boolean} |
|
*/ |
|
function checkSimpleExpression(range, version) { |
|
range = range.trim(); |
|
if (range === '') { |
|
return true; |
|
} |
|
|
|
const versionComponents = version.split(componentRegex); |
|
const { modifier, rangeComponents } = getModifierAndComponents(range); |
|
switch (modifier) { |
|
case '<': |
|
return checkLessThan(versionComponents, rangeComponents); |
|
case '<=': |
|
return checkLessThanOrEqual(versionComponents, rangeComponents); |
|
case '>=': |
|
return checkGreaterThanOrEqual(versionComponents, rangeComponents); |
|
case '>': |
|
return checkGreaterThan(versionComponents, rangeComponents); |
|
case '~': |
|
case '~>': |
|
return checkApproximateVersion(versionComponents, rangeComponents); |
|
default: |
|
return checkEqual(versionComponents, rangeComponents); |
|
} |
|
} |
|
|
|
/** |
|
* Checks whether `a` is less than `b`. |
|
* |
|
* @param {array<string>} a |
|
* @param {array<string>} b |
|
* @returns {boolean} |
|
*/ |
|
function checkLessThan(a, b) { |
|
return compareComponents(a, b) === -1; |
|
} |
|
|
|
/** |
|
* Checks whether `a` is less than or equal to `b`. |
|
* |
|
* @param {array<string>} a |
|
* @param {array<string>} b |
|
* @returns {boolean} |
|
*/ |
|
function checkLessThanOrEqual(a, b) { |
|
const result = compareComponents(a, b); |
|
return result === -1 || result === 0; |
|
} |
|
|
|
/** |
|
* Checks whether `a` is equal to `b`. |
|
* |
|
* @param {array<string>} a |
|
* @param {array<string>} b |
|
* @returns {boolean} |
|
*/ |
|
function checkEqual(a, b) { |
|
return compareComponents(a, b) === 0; |
|
} |
|
|
|
/** |
|
* Checks whether `a` is greater than or equal to `b`. |
|
* |
|
* @param {array<string>} a |
|
* @param {array<string>} b |
|
* @returns {boolean} |
|
*/ |
|
function checkGreaterThanOrEqual(a, b) { |
|
const result = compareComponents(a, b); |
|
return result === 1 || result === 0; |
|
} |
|
|
|
/** |
|
* Checks whether `a` is greater than `b`. |
|
* |
|
* @param {array<string>} a |
|
* @param {array<string>} b |
|
* @returns {boolean} |
|
*/ |
|
function checkGreaterThan(a, b) { |
|
return compareComponents(a, b) === 1; |
|
} |
|
|
|
/** |
|
* Checks whether `a` is "reasonably close" to `b` (as described in |
|
* https://www.npmjs.org/doc/misc/semver.html). For example, if `b` is "1.3.1" |
|
* then "reasonably close" is defined as ">= 1.3.1 and < 1.4". |
|
* |
|
* @param {array<string>} a |
|
* @param {array<string>} b |
|
* @returns {boolean} |
|
*/ |
|
function checkApproximateVersion(a, b) { |
|
const lowerBound = b.slice(); |
|
const upperBound = b.slice(); |
|
|
|
if (upperBound.length > 1) { |
|
upperBound.pop(); |
|
} |
|
const lastIndex = upperBound.length - 1; |
|
const numeric = parseInt(upperBound[lastIndex], 10); |
|
if (isNumber(numeric)) { |
|
upperBound[lastIndex] = numeric + 1 + ''; |
|
} |
|
|
|
return checkGreaterThanOrEqual(a, lowerBound) && checkLessThan(a, upperBound); |
|
} |
|
|
|
/** |
|
* Extracts the optional modifier (<, <=, =, >=, >, ~, ~>) and version |
|
* components from `range`. |
|
* |
|
* For example, given `range` ">= 1.2.3" returns an object with a `modifier` of |
|
* `">="` and `components` of `[1, 2, 3]`. |
|
* |
|
* @param {string} range |
|
* @returns {object} |
|
*/ |
|
function getModifierAndComponents(range) { |
|
const rangeComponents = range.split(componentRegex); |
|
const matches = rangeComponents[0].match(modifierRegex); |
|
invariant(matches, 'expected regex to match but it did not'); |
|
|
|
return { |
|
modifier: matches[1], |
|
rangeComponents: [matches[2]].concat(rangeComponents.slice(1)) |
|
}; |
|
} |
|
|
|
/** |
|
* Determines if `number` is a number. |
|
* |
|
* @param {mixed} number |
|
* @returns {boolean} |
|
*/ |
|
function isNumber(number) { |
|
return !isNaN(number) && isFinite(number); |
|
} |
|
|
|
/** |
|
* Tests whether `range` is a "simple" version number without any modifiers |
|
* (">", "~" etc). |
|
* |
|
* @param {string} range |
|
* @returns {boolean} |
|
*/ |
|
function isSimpleVersion(range) { |
|
return !getModifierAndComponents(range).modifier; |
|
} |
|
|
|
/** |
|
* Zero-pads array `array` until it is at least `length` long. |
|
* |
|
* @param {array} array |
|
* @param {number} length |
|
*/ |
|
function zeroPad(array, length) { |
|
for (let i = array.length; i < length; i++) { |
|
array[i] = '0'; |
|
} |
|
} |
|
|
|
/** |
|
* Normalizes `a` and `b` in preparation for comparison by doing the following: |
|
* |
|
* - zero-pads `a` and `b` |
|
* - marks any "x", "X" or "*" component in `b` as equivalent by zero-ing it out |
|
* in both `a` and `b` |
|
* - marks any final "*" component in `b` as a greedy wildcard by zero-ing it |
|
* and all of its successors in `a` |
|
* |
|
* @param {array<string>} a |
|
* @param {array<string>} b |
|
* @returns {array<array<string>>} |
|
*/ |
|
function normalizeVersions(a, b) { |
|
a = a.slice(); |
|
b = b.slice(); |
|
|
|
zeroPad(a, b.length); |
|
|
|
// mark "x" and "*" components as equal |
|
for (let i = 0; i < b.length; i++) { |
|
const matches = b[i].match(/^[x*]$/i); |
|
if (matches) { |
|
b[i] = a[i] = '0'; |
|
|
|
// final "*" greedily zeros all remaining components |
|
if (matches[0] === '*' && i === b.length - 1) { |
|
for (let j = i; j < a.length; j++) { |
|
a[j] = '0'; |
|
} |
|
} |
|
} |
|
} |
|
|
|
zeroPad(b, a.length); |
|
|
|
return [a, b]; |
|
} |
|
|
|
/** |
|
* Returns the numerical -- not the lexicographical -- ordering of `a` and `b`. |
|
* |
|
* For example, `10-alpha` is greater than `2-beta`. |
|
* |
|
* @param {string} a |
|
* @param {string} b |
|
* @returns {number} -1, 0 or 1 to indicate whether `a` is less than, equal to, |
|
* or greater than `b`, respectively |
|
*/ |
|
function compareNumeric(a, b) { |
|
const aPrefix = a.match(numericRegex)[1]; |
|
const bPrefix = b.match(numericRegex)[1]; |
|
const aNumeric = parseInt(aPrefix, 10); |
|
const bNumeric = parseInt(bPrefix, 10); |
|
|
|
if (isNumber(aNumeric) && isNumber(bNumeric) && aNumeric !== bNumeric) { |
|
return compare(aNumeric, bNumeric); |
|
} else { |
|
return compare(a, b); |
|
} |
|
} |
|
|
|
/** |
|
* Returns the ordering of `a` and `b`. |
|
* |
|
* @param {string|number} a |
|
* @param {string|number} b |
|
* @returns {number} -1, 0 or 1 to indicate whether `a` is less than, equal to, |
|
* or greater than `b`, respectively |
|
*/ |
|
function compare(a, b) { |
|
invariant(typeof a === typeof b, '"a" and "b" must be of the same type'); |
|
|
|
if (a > b) { |
|
return 1; |
|
} else if (a < b) { |
|
return -1; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
/** |
|
* Compares arrays of version components. |
|
* |
|
* @param {array<string>} a |
|
* @param {array<string>} b |
|
* @returns {number} -1, 0 or 1 to indicate whether `a` is less than, equal to, |
|
* or greater than `b`, respectively |
|
*/ |
|
function compareComponents(a, b) { |
|
const [aNormalized, bNormalized] = normalizeVersions(a, b); |
|
|
|
for (let i = 0; i < bNormalized.length; i++) { |
|
const result = compareNumeric(aNormalized[i], bNormalized[i]); |
|
if (result) { |
|
return result; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
var VersionRange = { |
|
/** |
|
* Checks whether `version` satisfies the `range` specification. |
|
* |
|
* We support a subset of the expressions defined in |
|
* https://www.npmjs.org/doc/misc/semver.html: |
|
* |
|
* version Must match version exactly |
|
* =version Same as just version |
|
* >version Must be greater than version |
|
* >=version Must be greater than or equal to version |
|
* <version Must be less than version |
|
* <=version Must be less than or equal to version |
|
* ~version Must be at least version, but less than the next significant |
|
* revision above version: |
|
* "~1.2.3" is equivalent to ">= 1.2.3 and < 1.3" |
|
* ~>version Equivalent to ~version |
|
* 1.2.x Must match "1.2.x", where "x" is a wildcard that matches |
|
* anything |
|
* 1.2.* Similar to "1.2.x", but "*" in the trailing position is a |
|
* "greedy" wildcard, so will match any number of additional |
|
* components: |
|
* "1.2.*" will match "1.2.1", "1.2.1.1", "1.2.1.1.1" etc |
|
* * Any version |
|
* "" (Empty string) Same as * |
|
* v1 - v2 Equivalent to ">= v1 and <= v2" |
|
* r1 || r2 Passes if either r1 or r2 are satisfied |
|
* |
|
* @param {string} range |
|
* @param {string} version |
|
* @returns {boolean} |
|
*/ |
|
contains(range, version) { |
|
return checkOrExpression(range.trim(), version.trim()); |
|
} |
|
}; |
|
|
|
module.exports = VersionRange; |