/** * @class ST.Version * * A utility class that wraps around a version number string and provides convenient methods * to perform comparisons. A version number is expressed in the following general format: * * major[.minor[.patch[.build[release]]]] * * The `Version` instance holds various readonly properties that contain the digested form * of the version string. The numeric componnets of `major`, `minor`, `patch` and `build` * as well as the textual suffix called `release`. * * Not depicted in the above syntax are three possible prefixes used to control partial * matching. These are '^' (the default), '>' and '~'. These are discussed below. * * Examples: * * var version = new ST.Version('1.0.2beta'); // or maybe "1.0" or "1.2.3.4RC" * console.log("Version is " + version); // Version is 1.0.2beta * * console.log(version.getMajor()); // 1 * console.log(version.getMinor()); // 0 * console.log(version.getPatch()); // 2 * console.log(version.getBuild()); // 0 * console.log(version.getRelease()); // beta * * The understood values of `release` are assigned numberic equivalents for the sake of * comparsion. The order of these from smallest to largest is as follows: * * * `"dev"` * * `"alpha"` or `"a"` * * `"beta"` or `"b"` * * `"RC"` or `"rc"` * * `"#"` * * `"pl"` or `"p"` * * Any other (unrecognized) suffix is consider greater than any of these. * * ## Comparisons * There are two forms of comparison that are commonly needed: full and partial. Full * comparison is simpler and is also the default. * * Example: * * var version = new ST.Version('1.0.2beta'); * * console.log(version.isGreaterThan('1.0.1')); // True * console.log(version.isGreaterThan('1.0.2alpha')); // True * console.log(version.isGreaterThan('1.0.2RC')); // False * console.log(version.isGreaterThan('1.0.2')); // False * console.log(version.isLessThan('1.0.2')); // True * * console.log(version.match(1.0)); // True (using a Number) * console.log(version.match('1.0.2')); // True (using a String) * * These comparisons are ultimately implemented by {@link ST.Version#compareTo compareTo} * which returns -1, 0 or 1 depending on whether the `Version' instance is less than, equal * to, or greater than the given "other" version. * * For example: * * var n = version.compareTo('1.0.1'); // == 1 (because 1.0.2beta > 1.0.1) * * n = version.compareTo('1.1'); // == -1 * n = version.compareTo(version); // == 0 * * ### Partial Comparisons * By default, unspecified version number fields are filled with 0. In other words, the * version number fields are 0-padded on the right or a "lower bound". This produces the * most commonly used forms of comparsion: * * var ver = new Version('4.2'); * * n = ver.compareTo('4.2.1'); // == -1 (4.2 promotes to 4.2.0 and is less than 4.2.1) * * There are two other ways to interpret comparisons of versions of different length. The * first of these is to change the padding on the right to be a large number (scuh as * Infinity) instead of 0. This has the effect of making the version an upper bound. For * example: * * var ver = new Version('^4.2'); // NOTE: the '^' prefix used * * n = ver.compareTo('4.3'); // == -1 (less than 4.3) * * n = ver.compareTo('4.2'); // == 1 (greater than all 4.2's) * n = ver.compareTo('4.2.1'); // == 1 * n = ver.compareTo('4.2.9'); // == 1 * * The second way to interpret this comparison is to ignore the extra digits, making the * match a prefix match. For example: * * var ver = new Version('~4.2'); // NOTE: the '~' prefix used * * n = ver.compareTo('4.3'); // == -1 * * n = ver.compareTo('4.2'); // == 0 * n = ver.compareTo('4.2.1'); // == 0 * * This final form can be useful when version numbers contain more components than are * important for certain comparisons. For example, the full version of Ext JS 4.2.1 is * "4.2.1.883" where 883 is the `build` number. * * This is how to create a "partial" `Version` and compare versions to it: * * var version421ish = new Version('~4.2.1'); * * n = version421ish.compareTo('4.2.1.883'); // == 0 * n = version421ish.compareTo('4.2.1.2'); // == 0 * n = version421ish.compareTo('4.2.1'); // == 0 * * n = version421ish.compareTo('4.2'); // == 1 * * In the above example, '4.2.1.2' compares as equal to '4.2.1' because digits beyond the * given "4.2.1" are ignored. However, '4.2' is less than the '4.2.1' prefix; its missing * digit is filled with 0. */ST.Version = ST.define(function (Version) { var // used by checkVersion to avoid temp arrays: endOfVersionRe = /([^\d\.])/, // eslint-disable-line notDigitsRe = /[^\d]/g, plusMinusRe = /[\-+]/g, // eslint-disable-line underscoreRe = /_/g; return { isVersion: true, constructor: function(version, defaultMode) { var me = this, padModes = me.padModes, ch, i, pad, parts, release, releaseStartIndex, ver; if (version.isVersion) { version = version.version; } me.version = ver = String(version).toLowerCase(). replace(underscoreRe, '.').replace(plusMinusRe, ''); ch = ver.charAt(0); if (ch in padModes) { ver = ver.substring(1); pad = padModes[ch]; } else { pad = defaultMode ? padModes[defaultMode] : 0; // careful - NaN is falsey! } me.pad = pad; releaseStartIndex = ver.search(endOfVersionRe); me.shortVersion = ver; if (releaseStartIndex !== -1) { me.release = release = ver.substr(releaseStartIndex, version.length); me.shortVersion = ver.substr(0, releaseStartIndex); if (Version.releaseValueMap) { release = Version.releaseValueMap[release] || release; } } me.releaseValue = release || pad; me.shortVersion = me.shortVersion.replace(notDigitsRe, ''); /** * @property {Number[]} parts * The split array of version number components found in the version string. * For example, for "1.2.3", this would be `[1, 2, 3]`. * @readonly * @private */ me.parts = parts = ver.split('.'); for (i = parts.length; i--; ) { parts[i] = parseInt(parts[i], 10); } if (pad === Infinity) { // have to add this to the end to create an upper bound: parts.push(pad); } /** * @property {Number} major * The first numeric part of the version number string. * @readonly */ me.major = parts[0] || pad; /** * @property {Number} [minor] * The second numeric part of the version number string. * @readonly */ me.minor = parts[1] || pad; /** * @property {Number} [patch] * The third numeric part of the version number string. * @readonly */ me.patch = parts[2] || pad; /** * @property {Number} [build] * The fourth numeric part of the version number string. * @readonly */ me.build = parts[3] || pad; return me; }, padModes: { '~': NaN, '^': Infinity }, /** * @property {String} [release=""] * The release level. The following values are understood: * * * `"dev"` * * `"alpha"` or `"a"` * * `"beta"` or `"b"` * * `"RC"` or `"rc"` * * `"#"` * * `"pl"` or `"p"` * @readonly */ release: '', /** * Compares this version instance to the specified `other` version. * * @param {String/Number/ST.Version} other The other version to which to compare. * @return {Number} -1 if this version is less than the target version, 1 if this * version is greater, and 0 if they are equal. */ compareTo: function (other) { // "lhs" == "left-hand-side" // "rhs" == "right-hand-side" var me = this, lhsPad = me.pad, lhsParts = me.parts, lhsLength = lhsParts.length, rhsVersion = other.isVersion ? other : new Version(other), rhsPad = rhsVersion.pad, rhsParts = rhsVersion.parts, rhsLength = rhsParts.length, length = Math.max(lhsLength, rhsLength), i, lhs, rhs; for (i = 0; i < length; i++) { lhs = (i < lhsLength) ? lhsParts[i] : lhsPad; rhs = (i < rhsLength) ? rhsParts[i] : rhsPad; // When one or both of the values are NaN these tests produce false // and we end up treating NaN as equal to anything. if (lhs < rhs) { return -1; } if (lhs > rhs) { return 1; } } // same comments about NaN apply here... lhs = me.releaseValue; rhs = rhsVersion.releaseValue; if (lhs < rhs) { return -1; } if (lhs > rhs) { return 1; } return 0; }, /** * Override the native `toString` method * @private * @return {String} version */ toString: function() { return this.version; }, /** * Override the native `valueOf` method * @private * @return {String} version */ valueOf: function() { return this.version; }, /** * Returns the major component value. * @return {Number} */ getMajor: function() { return this.major; }, /** * Returns the minor component value. * @return {Number} */ getMinor: function() { return this.minor; }, /** * Returns the patch component value. * @return {Number} */ getPatch: function() { return this.patch; }, /** * Returns the build component value. * @return {Number} */ getBuild: function() { return this.build; }, /** * Returns the release component text (e.g., "beta"). * @return {String} */ getRelease: function() { return this.release; }, /** * Returns the release component value for comparison purposes. * @return {Number/String} */ getReleaseValue: function() { return this.releaseValue; }, /** * Returns whether this version if greater than the supplied argument * @param {String/Number} target The version to compare with * @return {Boolean} `true` if this version if greater than the target, `false` otherwise */ isGreaterThan: function(target) { return this.compareTo(target) > 0; }, /** * Returns whether this version if greater than or equal to the supplied argument * @param {String/Number} target The version to compare with * @return {Boolean} `true` if this version if greater than or equal to the target, `false` otherwise */ isGreaterThanOrEqual: function(target) { return this.compareTo(target) >= 0; }, /** * Returns whether this version if smaller than the supplied argument * @param {String/Number} target The version to compare with * @return {Boolean} `true` if this version if smaller than the target, `false` otherwise */ isLessThan: function(target) { return this.compareTo(target) < 0; }, /** * Returns whether this version if less than or equal to the supplied argument * @param {String/Number} target The version to compare with * @return {Boolean} `true` if this version if less than or equal to the target, `false` otherwise */ isLessThanOrEqual: function(target) { return this.compareTo(target) <= 0; }, /** * Returns whether this version equals to the supplied argument * @param {String/Number} target The version to compare with * @return {Boolean} `true` if this version equals to the target, `false` otherwise */ equals: function(target) { return this.compareTo(target) === 0; }, /** * Returns whether this version matches the supplied argument. Example: * * var version = new ST.Version('1.0.2beta'); * console.log(version.match(1)); // true * console.log(version.match(1.0)); // true * console.log(version.match('1.0.2')); // true * console.log(version.match('1.0.2RC')); // false * * @param {String/Number} target The version to compare with * @return {Boolean} `true` if this version matches the target, `false` otherwise */ match: function(target) { target = String(target); return this.version.substr(0, target.length) === target; }, /** * Returns this format: [major, minor, patch, build, release]. Useful for comparison. * @return {Number[]} */ toArray: function() { var me = this; return [me.getMajor(), me.getMinor(), me.getPatch(), me.getBuild(), me.getRelease()]; }, /** * Returns shortVersion version without dots and release * @return {String} */ getShortVersion: function() { return this.shortVersion; }, /** * Convenient alias to {@link ST.Version#isGreaterThan isGreaterThan} * @param {String/Number/ST.Version} target * @return {Boolean} */ gt: function (target) { return this.compareTo(target) > 0; }, /** * Convenient alias to {@link ST.Version#isLessThan isLessThan} * @param {String/Number/ST.Version} target * @return {Boolean} */ lt: function (target) { return this.compareTo(target) < 0; }, /** * Convenient alias to {@link ST.Version#isGreaterThanOrEqual isGreaterThanOrEqual} * @param {String/Number/ST.Version} target * @return {Boolean} */ gtEq: function (target) { return this.compareTo(target) >= 0; }, /** * Convenient alias to {@link ST.Version#isLessThanOrEqual isLessThanOrEqual} * @param {String/Number/ST.Version} target * @return {Boolean} */ ltEq: function (target) { return this.compareTo(target) <= 0; }}});