UNPKG

@freeword/meta

Version:

Meta package for Freeword: exports all core types, constants, and utilities from the src/ directory.

174 lines 7.32 kB
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