@freeword/meta
Version:
Meta package for Freeword: exports all core types, constants, and utilities from the src/ directory.
174 lines • 7.32 kB
JavaScript
import _ /**/ from 'lodash';
import { ELLIPSIS_1GLYPH, ELLIPSIS_3DOTS } from "../Consts.js";
import { inspectify } from "./stringify.js";
import { throwable } from "./OutcomeUtils.js";
import { scrubVoid } from "./PropUtils.js";
const BRIEF_STRINGIFIER = (_vv) => inspectify(_vv, { naked: true, maxlen: 80 });
export function snipjoin(arr, { max = 8, joiner = ', ', stringifier = BRIEF_STRINGIFIER, yadayada = ', ...', shave = 1, ...rest } = {}) {
return toSentence(arr, { max, joiner, stringifier, shave, yadayada, lastJoiner: joiner, pairJoiner: joiner, ...rest });
}
export function briefSentence(arr, briefSentenceOpts = {}) {
const { max = 8, joiner = ', ', stringifier = BRIEF_STRINGIFIER, yadayada = ', ...', shave = 1, ...rest } = briefSentenceOpts;
return toSentence(arr, { max, joiner, stringifier, shave, yadayada, lastJoiner: joiner, pairJoiner: joiner, ...rest });
}
export function toSentence(arr, opts = {}) {
const conj = opts.conj ?? 'and';
const joiner = opts.joiner ?? ', ';
// const joinchar = joiner.trim
const { max: _max = Infinity, min = 0, shave = 0, whoa = '', empty = '', lastJoiner = `${joiner}${conj} `, pairJoiner = ` ${conj} `, yadayada = `${joiner}...${joiner}${conj} `, stringifier = BRIEF_STRINGIFIER, joiner: _j, conj: _c, ...rest } = opts;
const max = _.clamp(Number(_max), _.clamp(Number(min), 0, Infinity), Number(_max));
if (!_.isEmpty(rest)) {
throw throwable(`Need joiner, pairJoiner, lastJoiner, stringifier`, 'blanks', { keys: _.keys(rest) });
}
//
const joinable = manyAndLast(_.values(arr), { max, shave });
const { iam } = joinable;
if (iam === 'void') {
return empty;
}
if (iam === 'xnil') {
return empty + whoa;
}
if (iam === 'solo') {
return stringifier(joinable.first);
}
if (iam === 'xone') {
return stringifier(joinable.first) + whoa;
}
const last = stringifier(joinable.last);
if (iam === 'pair') {
return stringifier(joinable.first) + (pairJoiner ?? '') + last;
}
if (iam === 'xtwo') {
return stringifier(joinable.first) + (yadayada ?? '') + last + whoa;
}
const many = _.map(joinable.many, stringifier);
if (iam === 'many') {
return many.join(joiner) + (lastJoiner ?? '') + last;
}
// extra elements present. Even with two we want to indicate the overflow
return many.join(joiner) + (yadayada ?? '') + last + whoa;
}
const ManyAndLastKeys = ['void', 'solo', 'pair', 'many', 'xnil', 'xone', 'xtwo', 'xtra'];
// takes up to `max` entries from tne collection
export function manyAndLast(clxn, { max = Infinity, shave = 0 } = {}) {
const arr = _.values(clxn);
const arrlen = arr.length;
if (arrlen <= max) {
if (!(arrlen > 0)) {
return { iam: 'void' };
}
if (arrlen === 1) {
return { iam: 'solo', first: arr[0] };
}
const last = arr.pop();
if (arrlen === 2) {
return { iam: 'pair', first: arr[0], last };
}
return { iam: 'many', many: arr, last };
}
if (!(max >= 1)) {
return { iam: 'xnil' };
}
const cap = (shave > 0) ? _.clamp(max - shave, 1, max) : max;
if (cap <= 1) {
return { iam: 'xone', first: arr[0] };
}
const last = _.last(arr);
if (cap === 2) {
return { iam: 'xtwo', first: arr[0], last };
}
return { iam: 'xtra', many: _.take(arr, cap - 1), last };
}
export function someManyAndLast(clxn, opts = {}) {
const result = manyAndLast(clxn, opts);
const parts = result;
const body = parts.many ? parts.many : parts.first ? [parts.first] : [];
const tail = [];
const ellipsize = (parts.iam === 'xtra' || parts.iam === 'xtwo');
// const some: VT[] = parts.many ?? []
// if ('first' in parts) { some.unshift(parts.first as VT) }
// if ('last' in parts) { some.push(parts.last as VT) }
if ('last' in parts) {
if (ellipsize) {
tail.push(parts.last);
}
else {
body.push(parts.last);
}
}
const some = [...body, ...tail];
const postsize = some.length;
return { ...result, some, body, tail, postsize, ellipsize };
}
export function hardcapList(clxn, opts = {}) {
const copts = { max: 7, shave: 1, yadayada: '…', ...opts };
const result = manyAndLast(clxn, copts);
const { iam } = result;
switch (iam) {
case 'void': return clxn;
case 'solo': return clxn;
case 'pair': return clxn;
case 'xnil': return [];
case 'xone': return [result.first];
case 'many': return clxn;
case 'xtwo': return [result.first, copts.yadayada, result.last];
case 'xtra': return [...result.many, copts.yadayada, result.last];
default: throw throwable('Unreachable state from manyAndLast', 'unknownTag', { result, clxn, ManyAndLastKeys });
}
}
// sugar for calling shorten with the fancy `…` single character rather
// than three '...' dots. Note: `…` will have catastrophic effects on the
// size of an SMS, do not do that.
export function shortenWithEllipsis(str, maxlen = 29) {
return shorten(str, maxlen, { tail: ELLIPSIS_1GLYPH });
}
export function shorten(str, maxlen = 29, opts = {}) {
const { tail = ELLIPSIS_3DOTS } = opts;
if (!str) {
return '';
}
if (str.trim().length <= maxlen) {
return str.trim();
} // if it's already legal return it
//
if (maxlen <= 16 || (!tail)) {
return str.slice(0, maxlen).trim();
} // Don't get fancy with ellipsis if it's short or no ellipsis is to be used
//
const taillen = tail.length;
const breaklen = maxlen - (12 + taillen);
const most = str.slice(0, breaklen); // Keep most of it around, and
let rest = str.slice(breaklen, maxlen - taillen); // shorten the tail to one longer than maxlen + ellipsis
if (/\w$/.test(rest)) { // if the last character is part of a word,
rest = rest.replace(/\w+$/, ''); // chop off the partial word
}
rest = rest.replace(/\W+$/, ''); // and in any case trim non-word characters from the end
const shorter = `${most}${rest}${tail}`; // finally, attach an ellipsis
//
return shorter.trim();
}
// Strip blank/undefined/null strings from the args and string-join them with sep
export function smush(sep, ...rest) {
return scrubVoid(rest).join(sep);
}
export function qt(val) { return `'${val.replaceAll('\'', '\\\'')}'`; }
export function dqt(val) { return `"${val.replaceAll('"', '\\"')}"`; }
export function qtc(val) { return comma(qt(val)); }
export function comma(val) { return val + ','; }
function indentStr(by = 2) {
return _.isNumber(by) ? _.repeat(' ', by) : by;
}
export function indent(text, by = 2) {
const indenting = indentStr(by);
return _.trim(text)
.split(/\n/g)
.map((line) => (indenting + line).replace(/^ +$/, "")) // replace lines of all spaces with empty string; tabs, \v, other whitespace are not touched
.join("\n");
}
/** remove the leading indent from each line of the text */
export function dedent(text, by = 2) {
const indenting = indentStr(by);
return text.replace(indenting, '').replaceAll('\n' + indenting, '\n');
}
//# sourceMappingURL=StringUtils.js.map