UNPKG

@travetto/yaml

Version:

Simple YAML support, provides only clean subset of yaml

130 lines (120 loc) 4.03 kB
import { ObjectUtil } from '@travetto/base'; type SerializableType = (Error & { stack: SerializableType }) | RegExp | Function | Set<unknown> | Map<string, unknown> | number | boolean | null | string | object; type SerializeConfig = { indent: number; wordwrap: number; }; /** * Handles serialization of object to YAML output */ export class Serializer { /** * Clean value, escaping as needed */ static clean(key: string): string { if (/[#'"@ \-:{}]/.test(key)) { return key.includes('"') ? `'${key.replace(/[']/g, '\\\'')}'` : `"${key.replace(/["]/g, '\\"')}"`; } else if (!key) { return "''"; } else { return key; } } /** * Perform word wrap on text, with a given width */ static wordWrap(text: string, width: number = 120): string[] { const lines: string[] = []; let line: string[] = []; let runningLength: number = 0; const push = (x: string): void => { runningLength += x.length; line.push(x); }; const flushLine = (): void => { if (line.length > 0) { lines.push(line.join('').trimEnd()); } line = []; runningLength = 0; }; for (const sub of text.split(/\n/)) { if (sub.length < width) { push(sub); } else { const offset = sub.replace(/^(\s*)(.*)$/, (all, s) => s); push(offset); sub.trim().replace(/\S+(\s+|$)/g, part => { if (runningLength + part.length > width) { flushLine(); push(offset); } push(part); return ''; }); } flushLine(); } return lines; } /** * Serialize object with indentation and wordwrap support */ static serialize(o: SerializableType, config: Partial<SerializeConfig> = {}, indentLevel = 0): string { const cfg = { indent: 2, wordwrap: 120, ...config }; let out = ''; const prefix = ' '.repeat(indentLevel); if (o instanceof Date) { out = this.serialize(o.toISOString(), cfg, indentLevel); } else if (o instanceof Error) { out = `${this.serialize(o.stack, cfg, indentLevel + cfg.indent)}\n`; } else if (typeof o === 'function' || o instanceof RegExp) { if (ObjectUtil.hasToJSON(o)) { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions out = this.serialize(o.toJSON() as object, cfg, indentLevel); } else if (o instanceof Function) { out = this.serialize(o.Ⲑid ?? o.name, cfg, indentLevel); } else { throw new Error(`Types are not supported: ${typeof o}`); } } else if (o instanceof Set) { return this.serialize([...o], config, indentLevel); } else if (o instanceof Map) { return this.serialize(Object.fromEntries(o.entries()), config, indentLevel); } else if (Array.isArray(o)) { if (o.length) { out = o.map(el => `${prefix}-${this.serialize(el, cfg, indentLevel + cfg.indent)}`).join('\n'); if (indentLevel > 0) { out = `\n${out}`; } } else { out = ' []'; } } else if (typeof o === 'number' || typeof o === 'boolean' || o === null) { out = ` ${o}`; } else if (typeof o === 'string') { o = o.replace(/\t/g, prefix); const lines = this.wordWrap(o, cfg.wordwrap); if (lines.length > 1) { out = [' >', ...lines.map(x => `${prefix}${x}`)].join('\n'); } else { out = ` ${this.clean(lines[0])}`; } } else if (o !== undefined) { const fin = o; // eslint-disable-next-line @typescript-eslint/consistent-type-assertions const keys = Object.keys(fin) as (keyof typeof fin)[]; if (keys.length) { out = keys .filter(x => fin[x] !== undefined) .map(x => `${prefix}${this.clean(x)}:${this.serialize(fin[x], cfg, indentLevel + cfg.indent)}`) .join('\n'); if (indentLevel > 0) { out = `\n${out}`; } } else { out = ' {}'; } } return out; } }