/**
* xethya-range
*
* Copyright © 2016 Joel A. Villarreal Bertoldi. All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE.txt file in the root directory of this source tree.
*/
/**
* Represents a range of number, described through boundaries.
*/
class Range {
/**
* Instantiates a Range object.
* @param {Number} lowerBound The minimum number in this range.
* @param {Number} upperBound The maximum number in this range.
* @constructor
* @throws {Error} If lowerBound and upperBound are the same.
*/
constructor(lowerBound, upperBound) {
if (typeof lowerBound === 'undefined') {
throw new Error('Range#constructor: lowerBound is required');
}
if (typeof lowerBound !== 'number') {
throw new Error('Range#constructor: lowerBound must be a Number');
}
if (typeof upperBound === 'undefined') {
throw new Error('Range#constructor: upperBound is required');
}
if (typeof upperBound !== 'number') {
throw new Error('Range#constructor: upperBound must be a Number');
}
if (lowerBound === upperBound) {
throw new Error('Range#constructor: lowerBound and upperBound cannot be equal');
}
/**
* @property {Number} lowerBound The minimum number in this range.
*/
this.lowerBound = Math.min(lowerBound, upperBound);
/**
* @property {Number} upperBound The maximum number in this range.
*/
this.upperBound = Math.max(lowerBound, upperBound);
}
/**
* Checks if a value is in the defined range.
*
* @param {Number} value Value to compare.
* @return {Boolean} true if in range, false otherwise.
* @function
*/
includes(value) {
if (typeof value === 'undefined') {
throw new Error('Range#includes: value is required');
}
if (typeof value !== 'number') {
throw new Error('Range#includes: value must be a Number');
}
return this.lowerBound <= value && value <= this.upperBound;
}
/**
* Converts the Range object to a string representation.
* @return {String}
* @function
*/
toString() {
return `${this.lowerBound.toString()} ~ ${this.upperBound.toString()}`;
}
/**
* Creates a Range from an array of two numbers.
*
* @param {Array.<Number>} values Boundaries of the range.
* @return {Range}
* @function
* @static
*/
static fromArray(values) {
if (!Array.isArray(values)) {
throw new Error('Range#fromArray: values must be an Array of 2 numerical elements');
}
if (values.length !== 2) {
throw new Error('Range#fromArray: values must be an Array of 2 numerical elements');
}
if (!values.every(value => typeof value === 'number')) {
throw new Error('Range#fromArray: values must be an Array of 2 numerical elements');
}
return new Range(Number(values[0]), Number(values[1]));
}
/**
* Creates a Range from a string-based notation.
*
* @param {String} notedRange A string representation of a Range,
* using delimiters. Accepted formats: x,y x;y x:y x~y.
* @return {Range}
* @function
* @static
*/
static fromNotation(notedRange) {
if (!notedRange) {
throw new Error('Range#fromNotation: notedRange must use one of these formats: '
+ 'x,y x;y x:y x~y');
}
if (typeof notedRange !== 'string') {
throw new Error('Range#fromNotation: notedRange must use one of these formats: '
+ 'x,y x;y x:y x~y');
}
let range;
const allowedDelimiters = [',', ';', ':', '~'];
if (!allowedDelimiters.some(delimiter => notedRange.includes(delimiter))) {
throw new Error('Range#fromNotation: notedRange must use one of these formats: '
+ 'x,y x;y x:y x~y');
}
let delimiterFound = false;
while (!delimiterFound) {
const delimiter = allowedDelimiters.shift();
delimiterFound = notedRange.includes(delimiter);
if (delimiterFound) {
const data = notedRange.split(delimiter).map(d => d.trim());
if (data.length !== 2) {
throw new Error('Range#fromNotation: notedRange must use one of these formats: '
+ 'x,y x;y x:y x~y');
} else {
range = Range.fromArray(data.map(d => Number(d)));
}
}
}
if (!range) {
throw new Error('Range#fromNotation: notedRange must use one of these formats: '
+ 'x,y x;y x:y x~y');
}
return range;
}
}
export default Range;