StringFormat.ts
178 lines
| 4.5 KiB
| video/mp2t
|
TypeScriptLexer
|
|
r134 | import { isPrimitive, isNull, isKeyof, get } from "../safe"; | ||
|
|
r55 | import { MapOf } from "../interfaces"; | ||
| type SubstFn = (name: string, format?: string) => string; | ||||
|
|
r115 | type TemplateFn = (subst: SubstFn) => string | undefined; | ||
|
|
r55 | type ConvertFn = (value: any, format?: string) => string; | ||
| const map = { | ||||
| "\\{": "&curlopen;", | ||||
| "\\}": "&curlclose;", | ||||
| "&": "&", | ||||
| "\\:": ":" | ||||
| }; | ||||
| const rev = { | ||||
| curlopen: "{", | ||||
| curlclose: "}", | ||||
| amp: "&", | ||||
| colon: ":" | ||||
| }; | ||||
| function espaceString(s: string) { | ||||
| if (!s) | ||||
| return s; | ||||
| return "'" + s.replace(/('|\\)/g, "\\$1").replace("\n", "\\n") + "'"; | ||||
| } | ||||
| function encode(s: string) { | ||||
| if (!s) | ||||
| return s; | ||||
|
|
r115 | return s.replace(/\\{|\\}|&|\\:|\n/g, m => isKeyof(m, map) ? map[m] : m); | ||
|
|
r55 | } | ||
| function decode(s: string) { | ||||
| if (!s) | ||||
| return s; | ||||
|
|
r115 | return s.replace(/&(\w+);/g, (m, $1) => isKeyof($1, rev) ? rev[$1] : m); | ||
|
|
r55 | } | ||
| function subst(s: string) { | ||||
| const i = s.indexOf(":"); | ||||
| let name: string; | ||||
|
|
r115 | let pattern: string | undefined; | ||
|
|
r55 | if (i >= 0) { | ||
| name = s.substr(0, i); | ||||
| pattern = s.substr(i + 1); | ||||
| } else { | ||||
| name = s; | ||||
| } | ||||
| if (pattern) | ||||
| return [ | ||||
| espaceString(decode(name)), | ||||
|
|
r115 | espaceString(decode(pattern)) | ||
| ]; | ||||
|
|
r55 | else | ||
| return [espaceString(decode(name))]; | ||||
| } | ||||
| function _compile(str: string) { | ||||
| if (!str) | ||||
| return () => void 0; | ||||
| const chunks = encode(str).split("{"); | ||||
| let chunk: string; | ||||
| const code = ["var result=[];"]; | ||||
| for (let i = 0; i < chunks.length; i++) { | ||||
| chunk = chunks[i]; | ||||
| if (i === 0) { | ||||
| if (chunk) | ||||
| code.push("result.push(" + espaceString(decode(chunk)) + | ||||
| ");"); | ||||
| } else { | ||||
| const len = chunk.indexOf("}"); | ||||
| if (len < 0) | ||||
| throw new Error("Unbalanced substitution #" + i); | ||||
| code.push("result.push(subst(" + | ||||
| subst(chunk.substr(0, len)).join(",") + "));"); | ||||
| if (chunk.length > len + 1) | ||||
| code.push("result.push(" + | ||||
| espaceString(decode(chunk.substr(len + 1))) + ");"); | ||||
| } | ||||
| } | ||||
| code.push("return result.join('');"); | ||||
| // the code for this function is generated from the template | ||||
| // tslint:disable-next-line:function-constructor | ||||
| return new Function("subst", code.join("\n")) as TemplateFn; | ||||
| } | ||||
| const cache: MapOf<TemplateFn> = {}; | ||||
| export function compile(template: string) { | ||||
| let compiled = cache[template]; | ||||
| if (!compiled) { | ||||
| compiled = _compile(template); | ||||
| cache[template] = compiled; | ||||
| } | ||||
| return compiled; | ||||
| } | ||||
|
|
r115 | function defaultConverter(value: any, pattern?: string) { | ||
|
|
r55 | if (pattern && pattern.toLocaleLowerCase() === "json") { | ||
|
|
r115 | const seen: any = []; | ||
|
|
r55 | return JSON.stringify(value, (k, v) => { | ||
| if (!isPrimitive(v)) { | ||||
| const id = seen.indexOf(v); | ||||
| if (id >= 0) | ||||
| return "@ref-" + id; | ||||
| else { | ||||
| seen.push(v); | ||||
|
|
r115 | return v.toString() as string; | ||
|
|
r55 | } | ||
| } else { | ||||
|
|
r115 | return isNull(v) ? "" : v.toString(); | ||
|
|
r55 | } | ||
| }, 2); | ||||
| } else if (isNull(value)) { | ||||
| return ""; | ||||
| } else if (value instanceof Date) { | ||||
| return value.toISOString(); | ||||
| } else { | ||||
|
|
r115 | return value.toString() as string; | ||
|
|
r55 | } | ||
| } | ||||
| export class Formatter { | ||||
| _converters: ConvertFn[]; | ||||
| constructor(converters?: ConvertFn[]) { | ||||
| this._converters = converters || []; | ||||
| this._converters.push(defaultConverter); | ||||
| } | ||||
|
|
r115 | convert(value: any, pattern?: string) { | ||
|
|
r55 | for (const c of this._converters) { | ||
| const res = c(value, pattern); | ||||
| if (!isNull(res)) | ||||
| return res; | ||||
| } | ||||
| return ""; | ||||
| } | ||||
| format(msg: string, ...args: any[]) { | ||||
| const template = compile(msg); | ||||
| return template((name, pattern) => { | ||||
|
|
r115 | const value = get(name, args); | ||
|
|
r55 | return !isNull(value) ? this.convert(value, pattern) : ""; | ||
| }); | ||||
| } | ||||
| compile(msg: string) { | ||||
| const template = compile(msg); | ||||
| return (...args: any[]) => { | ||||
| return template((name, pattern) => { | ||||
|
|
r115 | const value = get(name, args); | ||
|
|
r55 | return !isNull(value) ? this.convert(value, pattern) : ""; | ||
| }); | ||||
| }; | ||||
| } | ||||
| } | ||||
| const _default = new Formatter(); | ||||
| export function format(msg: string, ...args: any[]) { | ||||
| return _default.format(msg, ...args); | ||||
| } | ||||
|
|
r65 | |||
| export function convert(value: any, pattern: string) { | ||||
| return _default.format(value, pattern); | ||||
| } | ||||
