# HG changeset patch # User cin # Date 2018-12-23 20:10:09 # Node ID 2fd5aee498884b33f89cf658075a5b28fcd57cec # Parent 5fd4258909e89da9f3099910059f9cb35c545805 text/format refactoring diff --git a/src/amd/ts/format.ts b/src/amd/ts/format.ts new file mode 100644 --- /dev/null +++ b/src/amd/ts/format.ts @@ -0,0 +1,40 @@ +import { format as dojoFormatNumber } from "dojo/number"; +import { format as dojoFormatDate } from "dojo/date/locale"; + +import { isNumber } from "./safe"; + +interface NumberFormatOptions { + round?: number; + pattern?: string; +} + +function formatNumber(value: any, pattern: string) { + if (isNumber(value)) { + const nopt = {} as NumberFormatOptions; + if (pattern.indexOf("!") === 0) { + nopt.round = -1; + pattern = pattern.substr(1); + } + nopt.pattern = pattern; + + return dojoFormatNumber(value, nopt); + } +} + +function formatDate(value: any, pattern: string) { + if (value instanceof Date) { + const m = pattern.match(/^(\w+)-(\w+)$/); + if (m) + return dojoFormatDate(value, { + selector: m[2], + formatLength: m[1] + }); + else if (pattern === "iso") + return value.toISOString(); + else + return dojoFormatDate(value, { + selector: "date", + datePattern: pattern + }); + } +} diff --git a/src/main/ts/components/AsyncComponent.ts b/src/main/ts/components/AsyncComponent.ts --- a/src/main/ts/components/AsyncComponent.ts +++ b/src/main/ts/components/AsyncComponent.ts @@ -7,19 +7,19 @@ export class AsyncComponent implements I _completion: Promise = Promise.resolve(); - getCompletion() { return this._completion }; + getCompletion() { return this._completion; } runOperation(op: (ct: ICancellation) => any, ct: ICancellation = Cancellation.none) { // create inner cancellation bound to the passed cancellation token let h: IDestroyable; - let inner = new Cancellation(cancel => { + const inner = new Cancellation(cancel => { this._cancel = cancel; h = ct.register(cancel); }); // TODO create cancellation source here - let guard = async () => { + const guard = async () => { try { await op(inner); } finally { @@ -28,7 +28,7 @@ export class AsyncComponent implements I destroy(h); this._cancel = null; } - } + }; return this._completion = guard(); } @@ -37,4 +37,4 @@ export class AsyncComponent implements I if (this._cancel) this._cancel(reason); } -} \ No newline at end of file +} diff --git a/src/main/ts/log/TraceSource.ts b/src/main/ts/log/TraceSource.ts --- a/src/main/ts/log/TraceSource.ts +++ b/src/main/ts/log/TraceSource.ts @@ -1,6 +1,6 @@ -import * as format from "../text/format"; import { Observable } from "../Observable"; import { Registry } from "./Registry"; +import { format } from "../text/FormatString"; export const DebugLevel = 400; @@ -47,7 +47,7 @@ export class TraceSource { debug(msg: string, ...args: any[]) { if (this.isEnabled(DebugLevel)) - this.emit(DebugLevel, format.apply(null, arguments)); + this.emit(DebugLevel, format(msg, args)); } isLogEnabled() { @@ -56,7 +56,7 @@ export class TraceSource { log(msg: string, ...args: any[]) { if (this.isEnabled(LogLevel)) - this.emit(LogLevel, format.apply(null, arguments)); + this.emit(LogLevel, format(msg, args)); } isWarnEnabled() { @@ -65,7 +65,7 @@ export class TraceSource { warn(msg: string, ...args: any[]) { if (this.isEnabled(WarnLevel)) - this.emit(WarnLevel, format.apply(null, arguments)); + this.emit(WarnLevel, format(msg, args)); } /** diff --git a/src/main/ts/text/FormatString.ts b/src/main/ts/text/FormatString.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/text/FormatString.ts @@ -0,0 +1,160 @@ +import { isPrimitive, isNumber, isNull } from "../safe"; + +type SubstFn = (name: string, format?: string) => string; +type FormatFn = (subst: SubstFn) => string; +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; + return s.replace(/\\{|\\}|&|\\:|\n/g, m => map[m] || m); +} + +function decode(s: string) { + if (!s) + return s; + return s.replace(/&(\w+);/g, (m, $1) => rev[$1] || m); +} + +function subst(s: string) { + const i = s.indexOf(":"); + let name: string; + let pattern: string; + if (i >= 0) { + name = s.substr(0, i); + pattern = s.substr(i + 1); + } else { + name = s; + } + + if (pattern) + return [ + espaceString(decode(name)), + espaceString(decode(pattern))]; + 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 FormatFn; +} + +const cache = {} as { + [i: string]: FormatFn +}; + +export function compile(template: string) { + let compiled = cache[template]; + if (!compiled) { + compiled = _compile(template); + cache[template] = compiled; + } + return compiled; +} + +function convert(value: any, pattern) { + if (!pattern) + return value.toString(); + + if (pattern.toLocaleLowerCase() === "json") { + const seen = []; + return JSON.stringify(value, (k, v) => { + if (!isPrimitive(v)) { + const id = seen.indexOf(v); + if (id >= 0) + return "@ref-" + id; + else { + seen.push(v); + return v; + } + } else { + return v; + } + }, 2); + } + + defaultFormatter(value, pattern); +} + +function defaultFormatter(value: any, pattern: string) { + if (value instanceof Date) { + return value.toISOString(); + } else { + return value.toString(pattern); + } +} + +export function format(fmt: string, ...args: any[]) { + if (args.length === 0) + return fmt; + + const template = compile(fmt); + + return template((name, pattern) => { + const value = args[name]; + return !isNull(value) ? convert(value, pattern) : ""; + }); +} + +export function compileFormat(fmt: string) { + const template = compile(fmt); + + return (...args: any[]) => { + return template((name, pattern) => { + const value = args[name]; + return !isNull(value) ? convert(value, pattern) : ""; + }); + }; +} diff --git a/src/main/ts/text/format.d.ts b/src/main/ts/text/format.d.ts deleted file mode 100644 --- a/src/main/ts/text/format.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare function format(format: string, ...args: any[]): string; - -declare namespace format { - -} - -export = format; \ No newline at end of file