UNPKG

@thisisagile/easy

Version:

Straightforward library for building domain-driven microservice architectures

197 lines (153 loc) 5.51 kB
import { List, toList } from './List'; import { asString, capitalize, replaceAll, Text } from './Text'; import { toName } from './Constructor'; import { Optional } from './Types'; import { Get, ofGet } from './Get'; import { isEmpty, isNotEmpty } from './Is'; import { JsonValue } from './Json'; import { entries } from './Object'; export type TemplateOptions = { type?: Text; property?: Text; actual?: Text; subject?: Text }; export class Template implements Text { constructor( private readonly template: string, private readonly subject: unknown = {}, private readonly options: TemplateOptions = {} ) {} toString = (): string => { return entries(this.options) .reduce((t, [k]) => this.option(t, k), this.object()) .replace(' ', ' '); }; private readonly props = (tmpl: string, key: string, result: List<string> = toList()): string[] => { const i1 = tmpl.indexOf(`{${key}`); if (i1 < 0) { return result; } const i2 = tmpl.indexOf('}', i1); return this.props(tmpl.slice(i2 + 1), key, result.add(tmpl.substring(i1 + 1, i2))); }; private readonly object = (): string => { return this.props(this.template, 'this').reduce((t: string, p) => t.replace(`{${p}}`, textValue(this.subject, p.replace('this.', ''))), this.template); }; private readonly option = (tmpl: string, prop: string): string => { return this.props(tmpl, prop).reduce((t: string, p) => t.replace(`{${p}}`, textValue(this.options, p)), tmpl); }; } export function template(tmpl: Text, subject: unknown, options: TemplateOptions = {}): Text { return new Template(asString(tmpl), subject, { type: toName(subject), subject: text(JSON.stringify(subject)), ...options, }); } export class ToText implements Text { constructor(readonly subject: string) {} get cap(): ToText { return this.map(s => capitalize(s?.toLowerCase())); } get capFirst(): ToText { return this.map(s => capitalize(s)); } get title(): ToText { return this.map(s => s .split(' ') .map(w => text(w).cap) .join(' ') ); } get pascal(): ToText { return this.title.replace(' ', ''); } get lower(): ToText { return this.map(s => s.toLowerCase()); } get upper(): ToText { return this.map(s => s.toUpperCase()); } get camel(): ToText { return this.title.trim.map(s => `${s.charAt(0).toLowerCase()}${s.slice(1)}`); } get kebab(): ToText { return this.lower.replace(' ', '-'); } get strictKebab(): ToText { return this.map(s => s.replace(/[^a-z\d]+/gi, ' ').trim()).kebab; } get slug(): ToText { return this.map(s => s .replace(/ß/g, 'ss') .normalize('NFKD') .replace(/[\u0300-\u036F]/g, '') .toLowerCase() .replace(/[^a-z\d]+/g, '-') .replace(/(^-)|(-$)/g, '') ); } get snake(): ToText { return this.upper.replace(' ', '_'); } get plural(): ToText { return this.ifLike('') ?? this.add('s'); } get space(): ToText { return this.map(s => s.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/[_-]/g, ' ')); } get sentence(): ToText { return this.isEmpty ? this : this.map(s => `${s.charAt(0).toUpperCase()}${s.slice(1)}.`); } get initials(): ToText { return this.map(s => s .split(' ') .map(w => w[0]) .join('') ); } get trim(): ToText { return this.map(s => s.replace(/[- ,_#]/g, '')); } get isEmpty(): boolean { return isEmpty(this.toString()); } parse = (subject: unknown, options = {}): ToText => text(template(this.subject, subject, { type: toName(subject), ...options })); is = (...others: unknown[]): boolean => others.some(o => this.toString() === text(o).toString()); equals = this.is; isLike = (...others: unknown[]): boolean => others.some(o => this.trim.lower.is(text(o).trim.lower)); ifLike = (...others: unknown[]): Optional<this> => (this.isLike(...others) ? this : undefined); endsWith = (end?: unknown): boolean => this.subject.endsWith(asString(end)); startsWith = (end?: unknown): boolean => this.subject.startsWith(asString(end)); first = (n: number): ToText => this.map(s => s.substring(0, n)); last = (n: number): ToText => this.map(s => s.substring(s.length - n)); map = (func: Get<string, string>): ToText => text(ofGet(func, this.subject)); replace = (search: Text, replace: Text): ToText => this.map(s => replaceAll(s, search, replace)); add = (add?: unknown, separator = ''): ToText => this.map(s => (isNotEmpty(add) ? `${s}${separator}${text(add)}` : s)); with = (separator: string, ...other: unknown[]): ToText => this.map(s => toList(s) .add(...other.map(u => text(u).toString())) .filter(s => isNotEmpty(s)) .join(separator) ); split = (separator: string = ' '): List<string> => toList(this.subject.split(separator)); toString(): string { return this.subject; } toJSON(): JsonValue { return this.subject; } } export function text(subject?: unknown, alt = ''): ToText { const sub = subject ? asString(subject) : alt; return new ToText(sub !== '[object Object]' ? sub : ''); } export function textValue(subject: any, prop: string): string { const p = prop.split('.'); const root = subject?.[p[0]]; const initial = typeof root === 'object' && root !== null ? root : text(root); return p .splice(1) .reduce((t, s) => t?.[s], initial) ?.toString() ?? ''; }