@finos/legend-shared
Version:
Legend Studio shared utilities and helpers
193 lines • 7.71 kB
JavaScript
/**
* Copyright (c) 2020-present, Goldman Sachs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { stringify as losslessStringify, parse as losslessParse, isSafeNumber as lossIsSafeNumber, } from 'lossless-json';
import CSVParser, {} from 'papaparse';
import { assertNonNullable } from '../error/AssertionUtils.js';
export const capitalize = (value) => value.length >= 1
? `${value[0].toUpperCase()}${value.substring(1, value.length)}`
: value;
export const toSentenceCase = (value) => (value ?? '').trim().replace(/^(?:\w+)\b/u, (val) => capitalize(val));
export const TITLE_CASE_EXCEPTION_WORDS = [
// We roughly follow AP style for simplicity
// See https://en.wikipedia.org/wiki/Title_case
'a',
'an',
'the',
'but',
'by',
'nor',
'and',
'or',
'so',
'as',
'yet',
'for',
'in',
'nor',
'of',
'on',
'at',
'out',
'to',
'up',
];
export const toTitleCase = (value) => (value ?? '')
.trim()
.replace(/\b(?:\w+)\b/gu, (val) => {
// lowercase minor words (typically articles, short prepositions, and some conjunctions)
if (TITLE_CASE_EXCEPTION_WORDS.includes(val.toLowerCase())) {
return val.toLowerCase();
}
return capitalize(val);
})
// always capitalize first and last word
.replace(/^(?:\w+)\b/u, (val) => capitalize(val))
.replace(/\b(?:\w+)$/u, (val) => capitalize(val));
export const prettyCONSTName = (value) => {
if (!value) {
return '';
}
// This handles our constant naming convention, e.g. SOME_VALUE, __PRIVATE_VALUE__, etc.
if (value.trim().match(/^[A-Z_]+$/)) {
return toTitleCase(value.trim().replace(/_+/gu, ' ').toLowerCase());
}
return (capitalize(value.trim())
// NOTE: here we must use capturing group as we also need to capture the breakpoint/separator as chunks
.split(/(?<chunk>[A-Z][a-z]+|[0-9]+)/)
.map((chunk) => chunk.toUpperCase() === chunk
? chunk
: chunk.charAt(0).toUpperCase() + chunk.slice(1).toLowerCase())
.filter(Boolean)
.join(' ')
.replace(/_+/gu, ' ')
.replace(/\s+/gu, ' '));
};
export const tryToFormatJSONString = (value, tabSize = 2) => {
try {
return JSON.stringify(JSON.parse(value), undefined, tabSize);
}
catch {
return value;
}
};
export const tryToMinifyJSONString = (value) => {
try {
return JSON.stringify(JSON.parse(value));
}
catch {
return value.replace(/\n\s*/g, '');
}
};
/**
* NOTE: this splits a string value into an array of strings by using a
* delimiter of a comma if the string is only one line. However, if the
* string has multiple lines, the delimiter will not be applied. This is so
* that for an example input of multiple lines like
* One, Comma in One
* Two
* Three
* will still equal 3 elements (['One, Comma in One', 'Two', 'Three']) rather than 4
*/
export const parseCSVString = (value) => {
let parseData;
if (value.includes('\n')) {
parseData = value.trim().split(/\r?\n|\r|\n/g);
return parseData;
}
else {
const parseResult = CSVParser.parse(value.trim(), {
delimiter: ',',
});
parseData = parseResult.data.flat();
if (parseResult.errors.length) {
// if (
// parseResult.errors[0] &&
// parseResult.errors[0].code === 'UndetectableDelimiter' &&
// parseResult.errors[0].type === 'Delimiter' &&
// parseResult.data.length === 1
// ) {
// // NOTE: this happens when the user only put one item in the value input
// // we can go the other way by ensure the input has a comma but this is arguably neater
// // as it tinkers with the parser
// } else {
// // there were some parsing error, escape
// // NOTE: ideally, we could show a warning here
// }
return undefined;
}
else {
return parseData;
}
}
};
export const parseCSVFile = (file, config) => CSVParser.parse(file, config);
export const csvStringify = (value, config) => CSVParser.unparse(value, config);
/**
* One very common use case is that we get the JSON as response from the server than we will convert this to a string and persist
* in the protocol. As such, we have to make sure this string is safe in PURE grammar format, i.e. it will escape single quotes
* properly since PURE strings are wrapped in a pair of single quotes. The following pair of escape/unescape method does the simple job of
* converting a JSON string to/from PURE grammar string
*
* NOTE: this is slightly different than escaping/unescaping JS string or normal JSON string, this is conversion is actually simplier since
* the escaping is naturally handling by string conversion in Javascript, we just need to have special handling for the escaping of single-quotes
* See https://stackoverflow.com/questions/3020094/how-should-i-escape-strings-in-json
* See https://github.com/joliss/js-string-escape/blob/master/index.js
*/
export const fromGrammarString = (value) => value.replace(/\\'/gu, "'");
export const toGrammarString = (value) => value.replace(/'/gu, "\\'");
/**
* These are the sets of methods that helps with lossless conversion of JSON to/from text.
*
* The implementation of `JSON.parse` and `JSON.stringify` in Javascript is not lossless, e.g. values like 1.0 (a double) is automatically converted to 1 (an integer).
* This pairs of method will convert the JSON losslessly. One caveat is that numeric values are stored as LosslessNumber, a data type which stores the numeric value as a string.
*
* NOTE: One can perform regular operations with a LosslessNumber, and it will throw an error when this would result in losing information.
* But use this with discretion since it it does not result in the same object as `JSON.parse`
*/
export { losslessParse as parseLosslessJSON };
export { lossIsSafeNumber as isLossSafeNumber };
export const stringifyLosslessJSON = (val, replacer, space) => {
const result = losslessStringify(val, replacer, space);
assertNonNullable(result, `Can't stringify lossless JSON value`);
return result;
};
export const tryToFormatLosslessJSONString = (value, tabSize = 2) => {
try {
return stringifyLosslessJSON(losslessParse(value), undefined, tabSize);
}
catch {
return value;
}
};
export const tryToMinifyLosslessJSONString = (value) => {
try {
return tryToFormatLosslessJSONString(value, 0);
}
catch {
return value.replace(/\n\s*/g, '');
}
};
export const indent = (value, indentText) => value
.split('\n')
.map((line) => `${indentText}${line}`)
.join('\n');
export const quantify = (value, label, pluralForm) => value <= 0
? `no ${pluralForm ?? `${label}s`}`
: value > 1
? `${value} ${pluralForm ?? `${label}s`}`
: `1 ${label}`;
export const quantifyList = (val, label, pluralForm) => quantify(val.length, label, pluralForm);
//# sourceMappingURL=FormatterUtils.js.map