@thisisagile/easy
Version:
Straightforward library for building domain-driven microservice architectures
197 lines (153 loc) • 5.51 kB
text/typescript
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() ?? '';
}