blob: 6e2c50010559c370b05a051840207af3d5194ed6 [file] [log] [blame]
// Copyright (C) 2019 Apple Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
// BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
import {queryToParams} from '/assets/js/common.js';
// These are flipped delibrately, it makes the fromQuery function return configurations in an
// intuitive order.
const REQUIRED_MEMBERS = ['architecture', 'version', 'is_simulator', 'platform'];
const OPTIONAL_MEMBERS = ['flavor', 'style', 'model', 'version_name'];
const FILTERING_MEMBERS = ['sdk'];
const VERSION_OFFSET_CONSTANT = 1000;
const SDK_REGEX = /(\d+)([A-Z])(\d+)(.*)/;
// This class is very similar to the Python Configuration class
class Configuration {
static members() {
return [...FILTERING_MEMBERS, ...OPTIONAL_MEMBERS, ...REQUIRED_MEMBERS];
}
static fromQuery() {
const params = queryToParams(document.URL.split('?')[1]);
let configs = [new Configuration()];
Configuration.members().forEach(member => {
const argumentsForMember = params[member];
if (!argumentsForMember || argumentsForMember.length === 0)
return;
let originalConfigs = configs;
configs = [];
argumentsForMember.forEach(argument => {
originalConfigs.forEach(config => {
let newConfig = new Configuration(config);
newConfig[member] = argument;
configs.push(new Configuration(newConfig));
});
});
});
return configs;
}
static versionToInteger(version) {
if (typeof version === 'number')
return version;
let versionList = null;
if (typeof version === 'string')
versionList = version.split('.');
else {
if (!Array.isArray(version))
return null;
versionList = version;
}
if (versionList.length < 1 || versionList.length > 3)
return null;
let result = 0;
let index = 0;
while (index < 3) {
result *= VERSION_OFFSET_CONSTANT;
if (index < versionList.length)
result += parseInt(versionList[index]);
++index;
}
return result;
}
static integerToVersion(version) {
let result = [0, 0, 0];
for (let index = 0; index < result.length; ++index) {
result[result.length - (index + 1)] = version % VERSION_OFFSET_CONSTANT;
version = Math.floor(version / VERSION_OFFSET_CONSTANT);
}
return result.join('.');
}
static combine()
{
if (!arguments.length)
return new Configuration();
const combined = new Configuration(arguments[0]);
for (let i = 1; i < arguments.length; ++i) {
Configuration.members().forEach(member => {
if (arguments[i][member] !== combined[member])
combined[member] = null;
});
}
return combined;
}
constructor(json = {}) {
this.platform = json.platform ? json.platform : null;
this.version = json.version ? Configuration.versionToInteger(json.version) : null;
this.version_name = json.version_name ? json.version_name : null;
this.sdk = json.sdk ? json.sdk : null;
if (typeof json.is_simulator === 'string')
this.is_simulator = json.is_simulator.toLowerCase() === 'true' ? true : false;
else if (typeof json.is_simulator === 'boolean')
this.is_simulator = json.is_simulator;
else
this.is_simulator = null;
this.style = json.style ? json.style : null;
this.flavor = json.flavor ? json.flavor : null;
this.model = json.model ? json.model : null;
this.architecture = json.architecture ? json.architecture : null;
// Mid-year releases really need to be treated with an entirely different version_name. The only way to reliably
// identify such a release is with the SDK (version numbers have historically ranged between .2 and .4)
// While appending E to all realese with an SDK after a mid-year release isn't entirely correct, it's close enough
// that the user can quickly dicern the differences by inspecting the specific SDK differences
if (this.sdk && this.version_name && !(json instanceof Configuration)) {
const match = this.sdk.match(SDK_REGEX);
const ending = this.version_name.substring(this.version_name.length - 2)
if (match && ending !== ' E' && match[2].localeCompare('E') >= 0)
this.version_name = `${this.version_name} E`;
else if (!match)
console.error(`'${this.sdk}' does not match the SDK regular expression`);
}
}
toKey() {
let result = ''
if (this.platform != null)
result += ' ' + this.platform
if (this.version_name != null)
result += ' ' + this.version_name;
else if (this.version != null)
result += ' ' + Configuration.integerToVersion(this.version);
if (this.sdk != null)
result += ' (' + this.sdk + ')';
if (this.is_simulator != null && this.is_simulator)
result += ' Simulator';
if (this.style != null)
result += ' ' + this.style;
if (this.flavor != null)
result += ' ' + this.flavor;
if (this.model != null)
result += ' on ' + this.model;
if (this.architecture != null)
result += ' using ' + this.architecture;
if (result)
return result.substr(1);
return 'All';
}
toString() {
let result = '';
// Version name implies platform and version
if (this.version_name != null)
result += ' ' + this.version_name;
else {
if (this.platform != null)
result += ' ' + this.platform
if (this.version != null)
result += ' ' + Configuration.integerToVersion(this.version);
}
if (this.is_simulator != null && this.is_simulator)
result += ' Simulator';
if (this.flavor != null)
result += ' ' + this.flavor;
if (this.style != null) {
result += ' ' + this.style.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join('-');
} if (this.model != null)
result += ' on ' + this.model;
if (this.architecture != null)
result += ' with ' + this.architecture;
if (this.sdk != null)
result += ' (' + this.sdk + ')';
if (result)
return result.substr(1);
return 'All';
}
compare(configuration) {
if (configuration === null)
return 1;
const lst = [['platform'], ['is_simulator'], ['version_name', 'version'], ['flavor'], ['style'], ['architecture'], ['model']];
let result = 0;
lst.forEach(keyFamily => {
// Grouping keys into families allows keys to be ignored if another is defined.
// For example, if two configurations declare the same version name, despite having a different
// version number, they will be treated as identical
let wasKeyFamilyDefined = false;
keyFamily.forEach(key => {
if (result || wasKeyFamilyDefined)
return;
if (this[key] !== null && configuration[key] !== null) {
if (typeof this[key] === 'string') {
if (key === 'version_name' && this[key].startsWith(configuration[key]))
result = 0;
else
result = this[key].localeCompare(configuration[key]);
}
else if (typeof this[key] === 'number' || typeof this[key] === 'boolean')
result = this[key] - configuration[key];
else
console.error(typeof this[key] + ' is not a recognized configuration type')
wasKeyFamilyDefined = true;
}
});
});
return result;
}
compareSDKs(configuration) {
if (this.sdk === null || configuration.sdk === null)
return 0;
const thisMatch = this.sdk.match(SDK_REGEX);
const configurationMatch = configuration.sdk.match(SDK_REGEX);
if (!thisMatch && !configurationMatch) {
console.error(`'${this.sdk}' and '${configuration.sdk}' do not match the SDK regular expression`);
return this.sdk.localeCompare(configuration.sdk);
}
if (!thisMatch && configurationMatch)
return -1;
if (thisMatch && !configurationMatch)
return 1;
const majorDiff = parseInt(thisMatch[1]) - parseInt(configurationMatch[1]);
if (majorDiff)
return majorDiff;
const minorDiff = thisMatch[2].localeCompare(configurationMatch[2]);
if (minorDiff)
return minorDiff;
const buildDiff = parseInt(thisMatch[3]) - parseInt(configurationMatch[3]);
if (buildDiff)
return buildDiff;
return thisMatch[4].localeCompare(configurationMatch[4]);
}
toParams() {
let version_name = this.version_name;
const ending = this.version_name ? this.version_name.substring(this.version_name.length - 2) : null;
if (ending === ' E')
version_name = this.version_name.substring(0, this.version_name.length - 2)
return {
platform: [this.platform],
version:[this.version && !this.version_name ? Configuration.integerToVersion(this.version) : null],
version_name: [version_name],
is_simulator: [this.is_simulator === null ? null : (this.is_simulator ? 'True' : 'False')],
style: [this.style],
flavor: [this.flavor],
model: [this.model],
architecture: [this.architecture],
};
}
}
export {Configuration};