mirror of https://github.com/miraclx/freyr-js
104 lines
4.1 KiB
JavaScript
104 lines
4.1 KiB
JavaScript
class ParseError extends Error {}
|
|
|
|
/**
|
|
* Parse ranges in strings.
|
|
* Syntax: `[a][..[=][b]]`
|
|
* @param {string} spec
|
|
* @example (valid) `a`, `a..`, `..b`, `a..b`, `a..=`, `..=b`, `a..=b`
|
|
* @example (optional) ``, `..`, `..=`
|
|
* @returns {{min: string; max: string; inclusive: boolean; strict: boolean;}}
|
|
* - `min`: The minimum part of the range. E.g `5` in `5..10`
|
|
* - `max`: The maximum part of the range. E.g `10` in `5..10`
|
|
* - `inclusive`: Whether or not the maximum is a part of the range. E.g `true` in `5..=10`
|
|
* - `strict`: Whether or not the spec was not a range. E.g `true` in `7`
|
|
*/
|
|
function parseRange(spec) {
|
|
let [min, max] = [];
|
|
const sepIndex = spec.indexOf('..');
|
|
[min, max] = (~sepIndex ? [spec.slice(0, sepIndex), spec.slice(sepIndex + 2)] : [spec]).map(part => part.trim());
|
|
let inclusive = !!max && max.startsWith('=');
|
|
[min, max] = [min, inclusive ? (max ? max.slice(1) : min) : max].map(part => part || undefined);
|
|
const strict = !~sepIndex;
|
|
if (strict && !max) [max, inclusive] = [min, true];
|
|
return {min, max, inclusive, strict};
|
|
}
|
|
|
|
/**
|
|
* Parse a number-typed range
|
|
* @param {*} spec
|
|
* @param {*} strictSyntax Whether or not to throw on invalid parts
|
|
* @example (valid) `1`, `1..`, `..5`, `1..5`, `1..=`, `..=5`, `1..=5`
|
|
*/
|
|
parseRange.num = function parseNumRange(spec, strictSyntax = false) {
|
|
let {min, max, inclusive} = parseRange(spec);
|
|
[min = -Infinity, max = Infinity, inclusive = inclusive] = [min, max].map(part => part && parseInt(part, 10));
|
|
if (strictSyntax && [min, max].some(Number.isNaN)) throw new ParseError(`Invalid num range spec syntax \`${spec}\``);
|
|
return {parsed: {min, max, inclusive}, check: num => num >= min && (inclusive ? num <= max : num < max)};
|
|
};
|
|
|
|
/**
|
|
* Parse a duration oriented range
|
|
* @param {*} spec
|
|
* @param {*} strictSyntax Whether or not to throw on invalid parts
|
|
* @example (valid) `1s`, `00:30..`, `..3:40`, `20..1:25`, `1s..=60000ms`, `..=200s`, `2:30..=310000ms`
|
|
*/
|
|
parseRange.time = function parseTimeRange(spec, strictSyntax = false) {
|
|
const cast = val =>
|
|
val !== undefined
|
|
? val.includes(':')
|
|
? val.split(':').reduce((acc, time) => 60 * acc + +time) * 1000
|
|
: val.endsWith('h')
|
|
? parseInt(val.slice(0, -1), 10) * 3600000
|
|
: val.endsWith('m')
|
|
? parseInt(val.slice(0, -1), 10) * 60000
|
|
: val.endsWith('ms')
|
|
? parseInt(val.slice(0, -2), 10)
|
|
: val.endsWith('s')
|
|
? parseInt(val.slice(0, -1), 10) * 1000
|
|
: parseInt(val, 10) * 1000
|
|
: val;
|
|
let {min, max, inclusive} = parseRange(spec);
|
|
[min = -Infinity, max = Infinity, inclusive = inclusive] = [min, max].map(cast);
|
|
if (strictSyntax && [min, max].some(Number.isNaN)) throw new ParseError(`Invalid time range spec syntax \`${spec}\``);
|
|
return {parsed: {min, max, inclusive}, check: time => time >= min && (inclusive ? time <= max : time < max)};
|
|
};
|
|
|
|
function initTest() {
|
|
function test_num(spec, values) {
|
|
console.log('%j', spec);
|
|
const parseBlock = parseRange.num(spec);
|
|
console.log(parseBlock.parsed);
|
|
values.forEach(value => console.log(`[${value.toString().padStart(2)}] ${parseBlock.check(value)}`));
|
|
}
|
|
// jscpd:ignore-start
|
|
function test_time(spec, values) {
|
|
console.log('%j', spec);
|
|
const parseBlock = parseRange.time(spec);
|
|
console.log(parseBlock.parsed);
|
|
values.forEach(value => console.log(`[${value.toString().padStart(2)}] ${parseBlock.check(value)}`));
|
|
}
|
|
// jscpd:ignore-end
|
|
|
|
test_num(' ', [1, 2, 3]);
|
|
test_num('7 ', [6, 7, 8]);
|
|
test_num('.. ', [1, 2, 3]);
|
|
test_num('..= ', [4, 5, 6]);
|
|
test_num('3.. ', [2, 3, 4]);
|
|
test_num('..4 ', [3, 4, 5]);
|
|
test_num('..=4 ', [3, 4, 5]);
|
|
test_num('5..10', [4, 5, 9, 10, 11]);
|
|
test_num('3..=9', [2, 3, 8, 9, 10]);
|
|
// invalids
|
|
test_num('a..b ', [1, 2, 3]);
|
|
test_num('... ', [1, 2, 3]);
|
|
test_num('...=9', [8, 9, 10]);
|
|
|
|
test_time('3:30..3:35 ', [209999, 210000, 214999, 215000, 215001]);
|
|
test_time('3s..9s ', [2999, 3000, 8999, 9000, 9001]);
|
|
test_time('10s..=00:30', [9999, 10000, 29999, 30000, 30001]);
|
|
test_time('20..50s ', [19999, 20000, 49999, 50000, 50001]);
|
|
}
|
|
|
|
module.exports = parseRange;
|
|
if (require.main === module) initTest();
|