@cruncheevos/core
Version:
Parse and generate achievements and leaderboards for RetroAchievements.org
167 lines (166 loc) • 4.97 kB
JavaScript
// TODO: figure out typings if possible, Record<X, Y> => Record<Y, X>
export function invertObject(obj) {
return Object.entries(obj).reduce((prev, cur) => {
prev[cur[1]] = cur[0];
return prev;
}, {});
}
export function capitalizeWord(word) {
return word[0].toUpperCase() + word.slice(1);
}
export function formatNumberAsHex(num, upperCase = false) {
let rvalue = Math.abs(num).toString(16);
if (upperCase) {
rvalue = rvalue.toUpperCase();
}
return `${num < 0 ? '-' : ''}0x` + rvalue;
}
export function eatSymbols(str, ...args) {
return str
.map((x, i) => {
let arg = args[i];
if (typeof arg === 'string') {
arg = `"${arg}"`;
}
else if (typeof arg === 'symbol') {
arg = String(arg);
}
else if (i >= args.length) {
arg = '';
}
return x + arg;
})
.join('');
}
export function isObject(val) {
return Object.prototype.toString.call(val) === '[object Object]';
}
export function isNumber(val, opts = {}) {
if (val === null ||
typeof val === 'symbol' ||
typeof val === 'boolean' ||
(typeof val === 'string' && val.trim().length === 0)) {
return false;
}
val = Number(val);
if (Number.isNaN(val) || Number.isFinite(val) === false) {
return false;
}
if (opts.isInteger && Number.isInteger(val) === false) {
return false;
}
if (opts.isPositive && val < 0) {
return false;
}
return true;
}
export function deepFreeze(obj) {
for (const key in obj) {
const value = obj[key];
if (isObject(value)) {
deepFreeze(value);
}
else if (Array.isArray(value)) {
for (const x of value) {
if (isObject(x) || Array.isArray(x)) {
deepFreeze(x);
}
}
Object.freeze(value);
}
}
return Object.freeze(obj);
}
export function deepObjectCopy(obj) {
const copy = {};
for (const key in obj) {
const value = obj[key];
copy[key] = isObject(value) ? deepObjectCopy(value) : value;
}
return copy;
}
export function wrappedError(err, message) {
const wrappedError = new Error(message);
wrappedError.cause = err;
return wrappedError;
}
// based on: https://stackoverflow.com/a/14991797
export function parseCSV(str) {
const arr = [];
let inQuotes = false;
let col = 0;
for (let i = 0; i < str.length; i++) {
const cur = str[i];
arr[col] = arr[col] || '';
if (inQuotes && cur == '\\' && str[i + 1] == '"') {
arr[col] += '"';
i++;
continue;
}
if (cur == '"') {
inQuotes = !inQuotes;
continue;
}
if (cur == ':' && !inQuotes) {
col++;
continue;
}
arr[col] += cur;
}
return arr;
}
export function quoteIfHaveTo(str) {
return str.match(/[:"]/g) ? `"${str.replace(/"/g, '\\"')}"` : str;
}
export const validate = {
andNormalizeId(id, propertyName = 'id') {
const origId = id;
if (typeof id === 'string') {
if (id.trim().length === 0) {
throw new Error(`expected ${propertyName} as unsigned integer, but got ""`);
}
id = Number(id);
}
if (Number.isInteger(id) === false) {
throw new Error(`expected ${propertyName} as unsigned integer, but got ` + eatSymbols `${origId}`);
}
if (id < 0 || id >= Number.MAX_SAFE_INTEGER) {
throw new Error(`expected ${propertyName} to be within the range of 0x0 .. 0xFFFFFFFF, but got ` +
eatSymbols `${origId}`);
}
return id;
},
string(input, propertyName = 'title') {
if (typeof input !== 'string') {
throw new Error(`expected ${propertyName} as string, but got ` + eatSymbols `${input}`);
}
},
nonEmptyString(input, propertyName = 'title') {
if (typeof input !== 'string' || input.trim().length === 0) {
throw new Error(`expected ${propertyName} as non-empty string, but got ` + eatSymbols `${input}`);
}
},
};
export function indexToConditionGroupName(index) {
return index === 0 ? 'Core' : `Alt ${index}`;
}
/**
* Splits string into numeric chunks, little endian
*
* @example
* stringToNumberLE('abcde') // [ 1684234849, 101 ]
* // abcd = 0x64636261 = 1684234849
* // e = 0x65 = 101
*/
export function stringToNumberLE(input) {
const bytes = new TextEncoder().encode(input);
const values = [];
for (let i = 0; i < bytes.length; i += 4) {
const value = [...bytes.slice(i, i + 4)]
.reverse()
.map(x => x.toString(16).padStart(2, '0'))
.join('');
values.push(parseInt(value, 16));
}
return values;
}