| @@ -0,0 +1,100 | |||
|
|
1 | import { TextWriterBase } from "../text/TextWriterBase"; | |
|
|
2 | import { isNull, isNullOrEmptyString, isPrimitive } from "../safe"; | |
|
|
3 | import { NullConsole } from "./NullConsole"; | |
|
|
4 | ||
|
|
5 | interface LogConsole { | |
|
|
6 | debug(...args: any[]): void; | |
|
|
7 | log(...args: any[]): void; | |
|
|
8 | warn(...args: any[]): void; | |
|
|
9 | error(...args: any[]): void; | |
|
|
10 | } | |
|
|
11 | ||
|
|
12 | function hasConsole() { | |
|
|
13 | try { | |
|
|
14 | // tslint:disable-next-line:no-console | |
|
|
15 | return (typeof console !== "undefined" && typeof console.log === "function"); | |
|
|
16 | } catch { | |
|
|
17 | return false; | |
|
|
18 | } | |
|
|
19 | } | |
|
|
20 | ||
|
|
21 | function getConsole() { | |
|
|
22 | return hasConsole() ? console : NullConsole.instance; | |
|
|
23 | } | |
|
|
24 | ||
|
|
25 | export class ConsoleWriter extends TextWriterBase { | |
|
|
26 | static readonly default = new ConsoleWriter(getConsole()); | |
|
|
27 | ||
|
|
28 | private _buffer: any[]; | |
|
|
29 | ||
|
|
30 | private _out: LogConsole; | |
|
|
31 | private _level: keyof LogConsole; | |
|
|
32 | ||
|
|
33 | constructor(out?: LogConsole) { | |
|
|
34 | super(); | |
|
|
35 | this._out = out || NullConsole.instance; | |
|
|
36 | this._buffer = []; | |
|
|
37 | this._level = "log"; | |
|
|
38 | } | |
|
|
39 | ||
|
|
40 | getLogLevel() { | |
|
|
41 | return this._level; | |
|
|
42 | } | |
|
|
43 | ||
|
|
44 | setLogLevel(level: keyof LogConsole) { | |
|
|
45 | this._level = level; | |
|
|
46 | } | |
|
|
47 | ||
|
|
48 | /** Flushes the buffer to the console | |
|
|
49 | */ | |
|
|
50 | writeNewLine() { | |
|
|
51 | // group text chunks together, and let objects as is | |
|
|
52 | // ['a', 'b', {foo: 'bar'}, 'c', 'd'] -> ['ab', {foo: 'bar'}, 'cd'] | |
|
|
53 | // this will prevent from additional spaces to occur in the console | |
|
|
54 | // ['a', 'b'] will be printed as 'a b' rather then 'ab'. | |
|
|
55 | ||
|
|
56 | // console.log("writeLine", this._buffer); | |
|
|
57 | ||
|
|
58 | let offset = 0; | |
|
|
59 | const args = []; | |
|
|
60 | this._buffer.forEach((v, i) => { | |
|
|
61 | if (!isPrimitive(v)) { | |
|
|
62 | if (offset < i) | |
|
|
63 | args.push(i - offset > 1 ? this._buffer.slice(offset, i).join("") : this._buffer[offset]); | |
|
|
64 | args.push(v); | |
|
|
65 | offset = i + 1; | |
|
|
66 | } | |
|
|
67 | }); | |
|
|
68 | if (offset < this._buffer.length) | |
|
|
69 | args.push(this._buffer.slice(offset).join("")); | |
|
|
70 | ||
|
|
71 | // console.log("WriteLine", args); | |
|
|
72 | ||
|
|
73 | this._out[this._level].apply(this._out, args); | |
|
|
74 | ||
|
|
75 | this._buffer = []; | |
|
|
76 | } | |
|
|
77 | ||
|
|
78 | /** Adds a text chunk to the buffer. Buffer contents will be flushed when | |
|
|
79 | * the end of line will be printed. | |
|
|
80 | * | |
|
|
81 | * @param text The text to be added to the buffer. | |
|
|
82 | */ | |
|
|
83 | writeText(text: string) { | |
|
|
84 | this._buffer.push(text); | |
|
|
85 | } | |
|
|
86 | ||
|
|
87 | /** Wrotes the specified value to the buffer. | |
|
|
88 | * | |
|
|
89 | * @param value The value to be added to the buffer | |
|
|
90 | * @param spec The instructions how to format the value, is this parameter | |
|
|
91 | * is ommited the raw value will be added to the buffer and | |
|
|
92 | * passed directly to the console out. | |
|
|
93 | */ | |
|
|
94 | writeValue(value: any, spec?: string) { | |
|
|
95 | if (isNullOrEmptyString(spec)) | |
|
|
96 | this._buffer.push(value); | |
|
|
97 | else | |
|
|
98 | super.writeValue(value); | |
|
|
99 | } | |
|
|
100 | } | |
| @@ -0,0 +1,52 | |||
|
|
1 | export class NullConsole { | |
|
|
2 | static readonly instance = new NullConsole(); | |
|
|
3 | ||
|
|
4 | assert(condition?: boolean, message?: string, ...data: any[]): void { | |
|
|
5 | } | |
|
|
6 | clear(): void { | |
|
|
7 | } | |
|
|
8 | count(label?: string): void { | |
|
|
9 | } | |
|
|
10 | debug(message?: any, ...optionalParams: any[]): void { | |
|
|
11 | } | |
|
|
12 | dir(value?: any, ...optionalParams: any[]): void { | |
|
|
13 | } | |
|
|
14 | dirxml(value: any): void { | |
|
|
15 | } | |
|
|
16 | error(message?: any, ...optionalParams: any[]): void { | |
|
|
17 | } | |
|
|
18 | exception(message?: string, ...optionalParams: any[]): void { | |
|
|
19 | } | |
|
|
20 | group(groupTitle?: string, ...optionalParams: any[]): void { | |
|
|
21 | } | |
|
|
22 | groupCollapsed(groupTitle?: string, ...optionalParams: any[]): void { | |
|
|
23 | } | |
|
|
24 | groupEnd(): void { | |
|
|
25 | } | |
|
|
26 | info(message?: any, ...optionalParams: any[]): void { | |
|
|
27 | } | |
|
|
28 | log(message?: any, ...optionalParams: any[]): void { | |
|
|
29 | } | |
|
|
30 | markTimeline(label?: string): void { | |
|
|
31 | } | |
|
|
32 | profile(reportName?: string): void { | |
|
|
33 | } | |
|
|
34 | profileEnd(reportName?: string): void { | |
|
|
35 | } | |
|
|
36 | table(...tabularData: any[]): void { | |
|
|
37 | } | |
|
|
38 | time(label?: string): void { | |
|
|
39 | } | |
|
|
40 | timeEnd(label?: string): void { | |
|
|
41 | } | |
|
|
42 | timeStamp(label?: string): void { | |
|
|
43 | } | |
|
|
44 | timeline(label?: string): void { | |
|
|
45 | } | |
|
|
46 | timelineEnd(label?: string): void { | |
|
|
47 | } | |
|
|
48 | trace(message?: any, ...optionalParams: any[]): void { | |
|
|
49 | } | |
|
|
50 | warn(message?: any, ...optionalParams: any[]): void { | |
|
|
51 | } | |
|
|
52 | } | |
| @@ -0,0 +1,20 | |||
|
|
1 | import { TraceEvent, TraceSource } from "./TraceSource"; | |
|
|
2 | import { format } from "../text/StringBuilder"; | |
|
|
3 | ||
|
|
4 | export class TraceEventData implements TraceEvent { | |
|
|
5 | source: TraceSource; | |
|
|
6 | level: number; | |
|
|
7 | message: any; | |
|
|
8 | args?: any[]; | |
|
|
9 | ||
|
|
10 | constructor(source: TraceSource, level: number, message: any, args: any[]) { | |
|
|
11 | this.source = source; | |
|
|
12 | this.level = level; | |
|
|
13 | this.message = message; | |
|
|
14 | this.args = args; | |
|
|
15 | } | |
|
|
16 | ||
|
|
17 | toString() { | |
|
|
18 | return format(this.message, ...this.args); | |
|
|
19 | } | |
|
|
20 | } | |
| @@ -0,0 +1,41 | |||
|
|
1 | import { IObservable, IDestroyable, ICancellation } from "../../interfaces"; | |
|
|
2 | import { TraceEvent, LogLevel, WarnLevel, DebugLevel } from "../TraceSource"; | |
|
|
3 | import { Cancellation } from "../../Cancellation"; | |
|
|
4 | import { destroy, argumentNotNull } from "../../safe"; | |
|
|
5 | import { ConsoleWriter } from "../ConsoleWriter"; | |
|
|
6 | ||
|
|
7 | export class ConsoleLogger implements IDestroyable { | |
|
|
8 | private readonly _subscriptions = new Array<IDestroyable>(); | |
|
|
9 | private readonly _writer: ConsoleWriter; | |
|
|
10 | ||
|
|
11 | constructor(writer = ConsoleWriter.default) { | |
|
|
12 | argumentNotNull(writer, "writer"); | |
|
|
13 | this._writer = writer; | |
|
|
14 | } | |
|
|
15 | ||
|
|
16 | writeEvents(source: IObservable<TraceEvent>, ct: ICancellation = Cancellation.none) { | |
|
|
17 | const subscription = source.on(this.writeEvent.bind(this)); | |
|
|
18 | if (ct.isSupported()) { | |
|
|
19 | ct.register(subscription.destroy.bind(subscription)); | |
|
|
20 | } | |
|
|
21 | this._subscriptions.push(subscription); | |
|
|
22 | } | |
|
|
23 | ||
|
|
24 | writeEvent(next: TraceEvent) { | |
|
|
25 | if (next.level >= DebugLevel) { | |
|
|
26 | this._writer.setLogLevel("debug"); | |
|
|
27 | } else if (next.level >= LogLevel) { | |
|
|
28 | this._writer.setLogLevel("log"); | |
|
|
29 | } else if (next.level >= WarnLevel) { | |
|
|
30 | this._writer.setLogLevel("warn"); | |
|
|
31 | } else { | |
|
|
32 | this._writer.setLogLevel("error"); | |
|
|
33 | } | |
|
|
34 | this._writer.write("{0}: ", next.source.id); | |
|
|
35 | this._writer.writeLine(next.message, ...next.args); | |
|
|
36 | } | |
|
|
37 | ||
|
|
38 | destroy() { | |
|
|
39 | this._subscriptions.forEach(destroy); | |
|
|
40 | } | |
|
|
41 | } | |
| @@ -0,0 +1,30 | |||
|
|
1 | import { isPrimitive, isNull } from "../safe"; | |
|
|
2 | ||
|
|
3 | export class Converter { | |
|
|
4 | static readonly default = new Converter(); | |
|
|
5 | ||
|
|
6 | convert(value: any, pattern: string) { | |
|
|
7 | if (pattern && pattern.toLocaleLowerCase() === "json") { | |
|
|
8 | const seen = []; | |
|
|
9 | return JSON.stringify(value, (k, v) => { | |
|
|
10 | if (!isPrimitive(v)) { | |
|
|
11 | const id = seen.indexOf(v); | |
|
|
12 | if (id >= 0) | |
|
|
13 | return "@ref-" + id; | |
|
|
14 | else { | |
|
|
15 | seen.push(v); | |
|
|
16 | return v; | |
|
|
17 | } | |
|
|
18 | } else { | |
|
|
19 | return v; | |
|
|
20 | } | |
|
|
21 | }, 2); | |
|
|
22 | } else if (isNull(value)) { | |
|
|
23 | return ""; | |
|
|
24 | } else if (value instanceof Date) { | |
|
|
25 | return value.toISOString(); | |
|
|
26 | } else { | |
|
|
27 | return value.toString(); | |
|
|
28 | } | |
|
|
29 | } | |
|
|
30 | } | |
| @@ -0,0 +1,19 | |||
|
|
1 | import { TextWriter } from "../interfaces"; | |
|
|
2 | ||
|
|
3 | export class NullTextWriter implements TextWriter { | |
|
|
4 | static readonly instance = new NullTextWriter(); | |
|
|
5 | ||
|
|
6 | write(obj: any): void; | |
|
|
7 | write(format: string, ...args: any[]): void; | |
|
|
8 | write() { | |
|
|
9 | } | |
|
|
10 | ||
|
|
11 | writeLine(obj: any): void; | |
|
|
12 | writeLine(format: string, ...args: any[]): void; | |
|
|
13 | writeLine() { | |
|
|
14 | } | |
|
|
15 | ||
|
|
16 | writeValue(value: any, spec?: string): void; | |
|
|
17 | writeValue() { | |
|
|
18 | } | |
|
|
19 | } | |
| @@ -0,0 +1,48 | |||
|
|
1 | import { TextWriter } from "../interfaces"; | |
|
|
2 | import { FormatCompiler } from "./FormatCompiler"; | |
|
|
3 | import { isString, argumentNotNull } from "../safe"; | |
|
|
4 | import { Converter } from "./Converter"; | |
|
|
5 | ||
|
|
6 | const compiler = new FormatCompiler(); | |
|
|
7 | ||
|
|
8 | export abstract class TextWriterBase implements TextWriter { | |
|
|
9 | private _converter: Converter; | |
|
|
10 | ||
|
|
11 | constructor(converter = Converter.default) { | |
|
|
12 | argumentNotNull(converter, "converter"); | |
|
|
13 | this._converter = converter; | |
|
|
14 | } | |
|
|
15 | ||
|
|
16 | writeNewLine() { | |
|
|
17 | this.writeValue("\n"); | |
|
|
18 | } | |
|
|
19 | ||
|
|
20 | write(obj: any): void; | |
|
|
21 | write(format: string, ...args: any[]): void; | |
|
|
22 | write(format: any, ...args: any[]): void { | |
|
|
23 | if (args.length) { | |
|
|
24 | const compiled = compiler.compile(format); | |
|
|
25 | compiled(this, args); | |
|
|
26 | } else { | |
|
|
27 | this.writeValue(format); | |
|
|
28 | } | |
|
|
29 | } | |
|
|
30 | ||
|
|
31 | writeLine(obj?: any): void; | |
|
|
32 | writeLine(format: string, ...args: any[]): void; | |
|
|
33 | writeLine(): void { | |
|
|
34 | if (arguments.length) | |
|
|
35 | this.write.apply(this, arguments); | |
|
|
36 | this.writeNewLine(); | |
|
|
37 | } | |
|
|
38 | ||
|
|
39 | writeValue(value: any, spec?: string): void { | |
|
|
40 | this.writeText( | |
|
|
41 | isString(value) ? | |
|
|
42 | value : | |
|
|
43 | this._converter.convert(value, spec) | |
|
|
44 | ); | |
|
|
45 | } | |
|
|
46 | ||
|
|
47 | abstract writeText(text: string); | |
|
|
48 | } | |
| @@ -0,0 +1,86 | |||
|
|
1 | import { StringBuilder } from "@implab/core/text/StringBuilder"; | |
|
|
2 | import { test } from "./TestTraits"; | |
|
|
3 | import { MockConsole } from "./mock/MockConsole"; | |
|
|
4 | import { ConsoleWriter } from "@implab/core/log/ConsoleWriter"; | |
|
|
5 | ||
|
|
6 | test("String builder", async t => { | |
|
|
7 | const sb = new StringBuilder(); | |
|
|
8 | ||
|
|
9 | sb.write("hello"); | |
|
|
10 | t.equals(sb.toString(), "hello", "Write simple text"); | |
|
|
11 | ||
|
|
12 | sb.write(", "); | |
|
|
13 | sb.write("world!"); | |
|
|
14 | t.equals(sb.toString(), "hello, world!", "Append text"); | |
|
|
15 | ||
|
|
16 | sb.clear(); | |
|
|
17 | t.equals(sb.toString(), "", "Clear"); | |
|
|
18 | ||
|
|
19 | sb.write(1); | |
|
|
20 | t.equals(sb.toString(), "1", "Write number"); | |
|
|
21 | ||
|
|
22 | sb.clear(); | |
|
|
23 | sb.writeValue(0.123); | |
|
|
24 | t.equals(sb.toString(), "0.123", "Format number"); | |
|
|
25 | ||
|
|
26 | sb.clear(); | |
|
|
27 | sb.writeValue(new Date("2019-01-02T00:00:00.000Z")); | |
|
|
28 | t.equals(sb.toString(), "2019-01-02T00:00:00.000Z", "Format date (ISO)"); | |
|
|
29 | ||
|
|
30 | sb.clear(); | |
|
|
31 | sb.write("{0}", "hello"); | |
|
|
32 | t.equals(sb.toString(), "hello", "Simple format text"); | |
|
|
33 | ||
|
|
34 | sb.write(", {0}!", "world"); | |
|
|
35 | t.equals(sb.toString(), "hello, world!", "Append formatted text"); | |
|
|
36 | ||
|
|
37 | sb.clear(); | |
|
|
38 | sb.write("abc: {0:json}; {0.length}; {0.1} {{olo}}", ["a", "b", "c"]); | |
|
|
39 | t.equals(sb.toString(), 'abc: [\n "a",\n "b",\n "c"\n]; 3; b {olo}', "Format string with spec"); | |
|
|
40 | ||
|
|
41 | sb.clear(); | |
|
|
42 | t.throws(() => sb.write("}", 0), "Should die on bad format: '}'"); | |
|
|
43 | t.throws(() => sb.write("{", 0), "Should die on bad format: '{'"); | |
|
|
44 | t.throws(() => sb.write("{}", 0), "Should die on bad format: '{}'"); | |
|
|
45 | t.throws(() => sb.write("{:}", 0), "Should die on bad format: '{:}'"); | |
|
|
46 | t.throws(() => sb.write("{{0}", 0), "Should die on bad format: '{{0}'"); | |
|
|
47 | ||
|
|
48 | }); | |
|
|
49 | ||
|
|
50 | test("ConsoleWriter", t => { | |
|
|
51 | const mockConsole = new MockConsole(); | |
|
|
52 | const writer = new ConsoleWriter(mockConsole); | |
|
|
53 | ||
|
|
54 | writer.setLogLevel("log"); | |
|
|
55 | ||
|
|
56 | writer.writeLine("Hello, world!"); | |
|
|
57 | ||
|
|
58 | t.equals(mockConsole.getBuffer().length, 1, "One line should be written"); | |
|
|
59 | t.equals(mockConsole.getBuffer()[0].level, "log", "LogLevel should be 'log'"); | |
|
|
60 | t.deepEqual(mockConsole.getBuffer()[0].data, ["Hello, world!"], "The buffer should contain single string"); | |
|
|
61 | ||
|
|
62 | mockConsole.clear(); | |
|
|
63 | writer.setLogLevel("debug"); | |
|
|
64 | writer.write("Bring "); | |
|
|
65 | writer.write("the {0}!", "light"); | |
|
|
66 | t.equals(mockConsole.getBuffer().length, 0, "No line should be written"); | |
|
|
67 | writer.writeLine(); | |
|
|
68 | ||
|
|
69 | t.equals(mockConsole.getBuffer().length, 1, "One line should be written"); | |
|
|
70 | t.equals(mockConsole.getBuffer()[0].level, "debug", "LogLevel should be 'log'"); | |
|
|
71 | t.deepEqual(mockConsole.getBuffer()[0].data, ["Bring the light!"], "Should concatenate string parts together"); | |
|
|
72 | ||
|
|
73 | mockConsole.clear(); | |
|
|
74 | writer.writeLine("It's {0} o'clock, lets have some {1}!", { h: 5}, { title: "tee" }); | |
|
|
75 | ||
|
|
76 | t.deepEqual(mockConsole.getBuffer()[0].data, ["It's ", { h: 5}, " o'clock, lets have some ", { title: "tee" }, "!"], "Non string parts should be psassed as is"); | |
|
|
77 | ||
|
|
78 | mockConsole.clear(); | |
|
|
79 | writer.writeLine("{0} or {1} to {2}", {i: 25}, 6, 4); | |
|
|
80 | t.deepEqual(mockConsole.getBuffer()[0].data, [{i: 25}, " or 6 to 4"], "25 or 6 to 4"); | |
|
|
81 | ||
|
|
82 | mockConsole.clear(); | |
|
|
83 | writer.writeLine("{0} or {1} to {2}! Let's have some {3}", 25, 6, 4, { product: "tee" } ); | |
|
|
84 | t.deepEqual(mockConsole.getBuffer()[0].data, ["25 or 6 to 4! Let's have some ", { product: "tee" }], "Should handle many text chunks and object at the end"); | |
|
|
85 | ||
|
|
86 | }); | |
| @@ -0,0 +1,52 | |||
|
|
1 | import {NullConsole} from "@implab/core/log/NullConsole"; | |
|
|
2 | ||
|
|
3 | interface ConsoleLineData { | |
|
|
4 | level: string; | |
|
|
5 | data: any[]; | |
|
|
6 | } | |
|
|
7 | ||
|
|
8 | export class MockConsole extends NullConsole { | |
|
|
9 | _buffer: ConsoleLineData[] = []; | |
|
|
10 | ||
|
|
11 | debug(...args: any[]) { | |
|
|
12 | this._buffer.push({ | |
|
|
13 | level: "debug", | |
|
|
14 | data: args | |
|
|
15 | }); | |
|
|
16 | } | |
|
|
17 | ||
|
|
18 | log(...args: any[]) { | |
|
|
19 | this._buffer.push({ | |
|
|
20 | level: "log", | |
|
|
21 | data: args | |
|
|
22 | }); | |
|
|
23 | } | |
|
|
24 | ||
|
|
25 | warn(...args: any[]) { | |
|
|
26 | this._buffer.push({ | |
|
|
27 | level: "warn", | |
|
|
28 | data: args | |
|
|
29 | }); | |
|
|
30 | } | |
|
|
31 | ||
|
|
32 | error(...args: any[]) { | |
|
|
33 | this._buffer.push({ | |
|
|
34 | level: "error", | |
|
|
35 | data: args | |
|
|
36 | }); | |
|
|
37 | } | |
|
|
38 | ||
|
|
39 | getBuffer() { | |
|
|
40 | return this._buffer; | |
|
|
41 | } | |
|
|
42 | ||
|
|
43 | getLine(i: number) { | |
|
|
44 | if (i >= this._buffer.length) | |
|
|
45 | throw new Error(`Line number ${i} is out of range, buffer.length = ${this._buffer.length}`); | |
|
|
46 | return this._buffer[i].data; | |
|
|
47 | } | |
|
|
48 | ||
|
|
49 | clear() { | |
|
|
50 | this._buffer = []; | |
|
|
51 | } | |
|
|
52 | } | |
| @@ -99,6 +99,8 def createSoursetTasks = { String name, | |||
|
|
99 | 99 | '-t', target, |
|
|
100 | 100 | '-m', jsmodule, |
|
|
101 | 101 | '-d', |
|
|
102 | '--sourceMap', | |
|
|
103 | '--sourceRoot', "file://$setDir/ts", | |
|
|
102 | 104 | '--outDir', compileDir, |
|
|
103 | 105 | '--declarationDir', declDir |
|
|
104 | 106 | |
| @@ -6,4 +6,4 description=Dependency injection, loggin | |||
|
|
6 | 6 | license=BSD-2-Clause |
|
|
7 | 7 | repository=https://bitbucket.org/implab/implabjs-core |
|
|
8 | 8 | npmScope=implab |
|
|
9 | npmName=core No newline at end of file | |
|
|
9 | npmName=core-amd No newline at end of file | |
| @@ -90,7 +90,7 | |||
|
|
90 | 90 | }, |
|
|
91 | 91 | "duplexer": { |
|
|
92 | 92 | "version": "0.1.1", |
|
|
93 |
"resolved": "http |
|
|
|
93 | "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", | |
|
|
94 | 94 | "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", |
|
|
95 | 95 | "dev": true |
|
|
96 | 96 | }, |
| @@ -144,7 +144,7 | |||
|
|
144 | 144 | "dependencies": { |
|
|
145 | 145 | "tape": { |
|
|
146 | 146 | "version": "2.3.3", |
|
|
147 |
"resolved": "http |
|
|
|
147 | "resolved": "http://registry.npmjs.org/tape/-/tape-2.3.3.tgz", | |
|
|
148 | 148 | "integrity": "sha1-Lnzgox3wn41oUWZKcYQuDKUFevc=", |
|
|
149 | 149 | "dev": true, |
|
|
150 | 150 | "requires": { |
| @@ -277,7 +277,7 | |||
|
|
277 | 277 | }, |
|
|
278 | 278 | "minimist": { |
|
|
279 | 279 | "version": "0.0.5", |
|
|
280 |
"resolved": "http |
|
|
|
280 | "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz", | |
|
|
281 | 281 | "integrity": "sha1-16oye87PUY+RBqxrjwA/o7zqhWY=", |
|
|
282 | 282 | "dev": true |
|
|
283 | 283 | }, |
| @@ -316,7 +316,7 | |||
|
|
316 | 316 | }, |
|
|
317 | 317 | "readable-stream": { |
|
|
318 | 318 | "version": "1.1.14", |
|
|
319 |
"resolved": "http |
|
|
|
319 | "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", | |
|
|
320 | 320 | "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", |
|
|
321 | 321 | "dev": true, |
|
|
322 | 322 | "requires": { |
| @@ -369,7 +369,7 | |||
|
|
369 | 369 | }, |
|
|
370 | 370 | "string_decoder": { |
|
|
371 | 371 | "version": "0.10.31", |
|
|
372 |
"resolved": "http |
|
|
|
372 | "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", | |
|
|
373 | 373 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", |
|
|
374 | 374 | "dev": true |
|
|
375 | 375 | }, |
| @@ -432,7 +432,7 | |||
|
|
432 | 432 | }, |
|
|
433 | 433 | "through2": { |
|
|
434 | 434 | "version": "0.2.3", |
|
|
435 |
"resolved": "http |
|
|
|
435 | "resolved": "http://registry.npmjs.org/through2/-/through2-0.2.3.tgz", | |
|
|
436 | 436 | "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", |
|
|
437 | 437 | "dev": true, |
|
|
438 | 438 | "requires": { |
| @@ -7,6 +7,23 const trace = TraceSource.get(m.id); | |||
|
|
7 | 7 | |
|
|
8 | 8 | type TemplateFn = (obj: object) => string; |
|
|
9 | 9 | |
|
|
10 | const htmlEscapes = { | |
|
|
11 | "&": "&", | |
|
|
12 | "<": "<", | |
|
|
13 | ">": ">", | |
|
|
14 | '"': """, | |
|
|
15 | "'": "'", | |
|
|
16 | "/": "/" | |
|
|
17 | }; | |
|
|
18 | ||
|
|
19 | // Regex containing the keys listed immediately above. | |
|
|
20 | const htmlEscaper = /[&<>"'\/]/g; | |
|
|
21 | ||
|
|
22 | // Escape a string for HTML interpolation. | |
|
|
23 | function escapeHtml(string: any) { | |
|
|
24 | return ("" + string).replace(htmlEscaper, match => htmlEscapes[match]); | |
|
|
25 | } | |
|
|
26 | ||
|
|
10 | 27 | export class TemplateCompiler { |
|
|
11 | 28 | |
|
|
12 | 29 | _data: string[]; |
| @@ -27,14 +44,14 export class TemplateCompiler { | |||
|
|
27 | 44 | |
|
|
28 | 45 | try { |
|
|
29 | 46 | // tslint:disable-next-line:function-constructor |
|
|
30 | const compiled = new Function("obj, format, $data", text); | |
|
|
47 | const compiled = new Function("obj, format, $data, escapeHtml", text); | |
|
|
31 | 48 | /** |
|
|
32 | 49 | * Функция форматирования по шаблону |
|
|
33 | 50 | * |
|
|
34 | 51 | * @type{Function} |
|
|
35 | 52 | * @param{Object} obj объект с параметрами для подстановки |
|
|
36 | 53 | */ |
|
|
37 | return (obj: object) => compiled(obj || {}, format, this._data); | |
|
|
54 | return (obj: object) => compiled(obj || {}, format, this._data, escapeHtml); | |
|
|
38 | 55 | } catch (e) { |
|
|
39 | 56 | trace.traceEvent(DebugLevel, [e, text, this._data]); |
|
|
40 | 57 | throw e; |
| @@ -69,6 +86,9 export class TemplateCompiler { | |||
|
|
69 | 86 | case TokenType.OpenInlineBlock: |
|
|
70 | 87 | this.visitInline(parser); |
|
|
71 | 88 | break; |
|
|
89 | case TokenType.OpenFilterBlock: | |
|
|
90 | this.visitFilter(parser); | |
|
|
91 | break; | |
|
|
72 | 92 | default: |
|
|
73 | 93 | this.visitTextFragment(parser); |
|
|
74 | 94 | break; |
| @@ -87,6 +107,17 export class TemplateCompiler { | |||
|
|
87 | 107 | this._code.push(code.join("")); |
|
|
88 | 108 | } |
|
|
89 | 109 | |
|
|
110 | visitFilter(parser: ITemplateParser) { | |
|
|
111 | const code = ["$p.push(escapeHtml("]; | |
|
|
112 | while (parser.next()) { | |
|
|
113 | if (parser.token() === TokenType.CloseBlock) | |
|
|
114 | break; | |
|
|
115 | code.push(parser.value()); | |
|
|
116 | } | |
|
|
117 | code.push("));"); | |
|
|
118 | this._code.push(code.join("")); | |
|
|
119 | } | |
|
|
120 | ||
|
|
90 | 121 | visitCode(parser: ITemplateParser) { |
|
|
91 | 122 | const code = []; |
|
|
92 | 123 | while (parser.next()) { |
| @@ -5,12 +5,13 import m = require("module"); | |||
|
|
5 | 5 | |
|
|
6 | 6 | const trace = TraceSource.get(m.id); |
|
|
7 | 7 | |
|
|
8 | const splitRx = /(<%=|\[%=|<%|\[%|%\]|%>)/; | |
|
|
8 | const splitRx = /(<%=|<%~|\[%~|\[%=|<%|\[%|%\]|%>)/; | |
|
|
9 | 9 | |
|
|
10 | 10 | export enum TokenType { |
|
|
11 | 11 | None, |
|
|
12 | 12 | Text, |
|
|
13 | 13 | OpenInlineBlock, |
|
|
14 | OpenFilterBlock, | |
|
|
14 | 15 | OpenBlock, |
|
|
15 | 16 | CloseBlock |
|
|
16 | 17 | } |
| @@ -20,6 +21,8 const tokenMap: MapOf<TokenType> = { | |||
|
|
20 | 21 | "[%": TokenType.OpenBlock, |
|
|
21 | 22 | "<%=": TokenType.OpenInlineBlock, |
|
|
22 | 23 | "[%=": TokenType.OpenInlineBlock, |
|
|
24 | "<%~": TokenType.OpenFilterBlock, | |
|
|
25 | "[%~": TokenType.OpenFilterBlock, | |
|
|
23 | 26 | "%>": TokenType.CloseBlock, |
|
|
24 | 27 | "%]": TokenType.CloseBlock |
|
|
25 | 28 | }; |
| @@ -101,11 +101,11 export interface IObserver<T> { | |||
|
|
101 | 101 | } |
|
|
102 | 102 | |
|
|
103 | 103 | export interface TextWriter { |
|
|
104 |
|
|
|
|
105 |
|
|
|
|
104 | write(obj: any): void; | |
|
|
105 | write(format: string, ...args: any[]): void; | |
|
|
106 | 106 | |
|
|
107 |
|
|
|
|
108 |
|
|
|
|
107 | writeLine(obj?: any): void; | |
|
|
108 | writeLine(format: string, ...args: any[]): void; | |
|
|
109 | 109 | |
|
|
110 |
|
|
|
|
110 | writeValue(value: any, spec?: string): void; | |
|
|
111 | 111 | } |
| @@ -1,6 +1,6 | |||
|
|
1 | 1 | import { Observable } from "../Observable"; |
|
|
2 | 2 | import { Registry } from "./Registry"; |
|
|
3 | import { format as _format } from "../text/StringFormat"; | |
|
|
3 | import { TraceEventData } from "./TraceEventData"; | |
|
|
4 | 4 | |
|
|
5 | 5 | export const DebugLevel = 400; |
|
|
6 | 6 | |
| @@ -17,13 +17,9 export interface TraceEvent { | |||
|
|
17 | 17 | |
|
|
18 | 18 | readonly level: number; |
|
|
19 | 19 | |
|
|
20 |
readonly a |
|
|
|
21 | } | |
|
|
20 | readonly message: any; | |
|
|
22 | 21 | |
|
|
23 | function format(msg) { | |
|
|
24 | if (typeof(msg) !== "string" || arguments.length === 1) | |
|
|
25 | return msg; | |
|
|
26 | return _format.apply(null, arguments); | |
|
|
22 | readonly args?: any[]; | |
|
|
27 | 23 | } |
|
|
28 | 24 | |
|
|
29 | 25 | export class TraceSource { |
| @@ -43,35 +39,41 export class TraceSource { | |||
|
|
43 | 39 | }); |
|
|
44 | 40 | } |
|
|
45 | 41 | |
|
|
46 |
protected emit(level: number, a |
|
|
|
47 |
this._notifyNext( |
|
|
|
42 | protected emit(level: number, message: any, args?: any[]) { | |
|
|
43 | this._notifyNext(new TraceEventData(this, level, message, args)); | |
|
|
48 | 44 | } |
|
|
49 | 45 | |
|
|
50 | 46 | isDebugEnabled() { |
|
|
51 | 47 | return this.level >= DebugLevel; |
|
|
52 | 48 | } |
|
|
53 | 49 | |
|
|
50 | debug(data: any): void; | |
|
|
51 | debug(msg: string, ...args: any[]): void; | |
|
|
54 | 52 | debug(msg: string, ...args: any[]) { |
|
|
55 | 53 | if (this.isEnabled(DebugLevel)) |
|
|
56 |
this.emit(DebugLevel, |
|
|
|
54 | this.emit(DebugLevel, msg, args); | |
|
|
57 | 55 | } |
|
|
58 | 56 | |
|
|
59 | 57 | isLogEnabled() { |
|
|
60 | 58 | return this.level >= LogLevel; |
|
|
61 | 59 | } |
|
|
62 | 60 | |
|
|
61 | log(data: any): void; | |
|
|
62 | log(msg: string, ...args: any[]): void; | |
|
|
63 | 63 | log(msg: string, ...args: any[]) { |
|
|
64 | 64 | if (this.isEnabled(LogLevel)) |
|
|
65 |
this.emit(LogLevel, |
|
|
|
65 | this.emit(LogLevel, msg, args); | |
|
|
66 | 66 | } |
|
|
67 | 67 | |
|
|
68 | 68 | isWarnEnabled() { |
|
|
69 | 69 | return this.level >= WarnLevel; |
|
|
70 | 70 | } |
|
|
71 | 71 | |
|
|
72 | warn(data: any): void; | |
|
|
73 | warn(msg: string, ...args: any[]): void; | |
|
|
72 | 74 | warn(msg: string, ...args: any[]) { |
|
|
73 | 75 | if (this.isEnabled(WarnLevel)) |
|
|
74 |
this.emit(WarnLevel, |
|
|
|
76 | this.emit(WarnLevel, msg, args); | |
|
|
75 | 77 | } |
|
|
76 | 78 | |
|
|
77 | 79 | /** |
| @@ -81,15 +83,20 export class TraceSource { | |||
|
|
81 | 83 | return this.level >= ErrorLevel; |
|
|
82 | 84 | } |
|
|
83 | 85 | |
|
|
86 | /** Traces a error | |
|
|
87 | * @param data The object which will be passed to the underlying listeners | |
|
|
88 | */ | |
|
|
89 | error(data: any): void; | |
|
|
84 | 90 | /** |
|
|
85 | 91 | * Traces a error. |
|
|
86 | 92 | * |
|
|
87 | 93 | * @param msg the message. |
|
|
88 | 94 | * @param args parameters which will be substituted in the message. |
|
|
89 | 95 | */ |
|
|
96 | error(msg: string, ...args: any[]): void; | |
|
|
90 | 97 | error(msg: string, ...args: any[]) { |
|
|
91 | 98 | if (this.isEnabled(ErrorLevel)) |
|
|
92 |
this.emit(ErrorLevel, |
|
|
|
99 | this.emit(ErrorLevel, msg, args); | |
|
|
93 | 100 | } |
|
|
94 | 101 | |
|
|
95 | 102 | /** |
| @@ -106,11 +113,11 export class TraceSource { | |||
|
|
106 | 113 | * Traces a raw event, passing data as it is to the underlying listeners |
|
|
107 | 114 | * |
|
|
108 | 115 | * @param level the level of the event |
|
|
109 |
* @param |
|
|
|
116 | * @param msg the data of the event, can be a simple string or any object. | |
|
|
110 | 117 | */ |
|
|
111 |
traceEvent(level: number, |
|
|
|
118 | traceEvent(level: number, msg: any, ...args: any[]) { | |
|
|
112 | 119 | if (this.isEnabled(level)) |
|
|
113 | this.emit(level, arg); | |
|
|
120 | this.emit(level, msg, args); | |
|
|
114 | 121 | } |
|
|
115 | 122 | |
|
|
116 | 123 | /** |
| @@ -1,49 +1,1 | |||
|
|
1 | import { IObservable, IDestroyable, ICancellation } from "../../interfaces"; | |
|
|
2 | import { TraceEvent, LogLevel, WarnLevel, DebugLevel } from "../TraceSource"; | |
|
|
3 | import { Cancellation } from "../../Cancellation"; | |
|
|
4 | import { destroy } from "../../safe"; | |
|
|
5 | ||
|
|
6 | function hasConsole() { | |
|
|
7 | try { | |
|
|
8 | // tslint:disable-next-line:no-console | |
|
|
9 | return (typeof console !== "undefined" && typeof console.log === "function"); | |
|
|
10 | } catch { | |
|
|
11 | return false; | |
|
|
12 | } | |
|
|
13 | } | |
|
|
14 | ||
|
|
15 | export class ConsoleWriter implements IDestroyable { | |
|
|
16 | readonly _subscriptions = new Array<IDestroyable>(); | |
|
|
17 | ||
|
|
18 | writeEvents(source: IObservable<TraceEvent>, ct: ICancellation = Cancellation.none) { | |
|
|
19 | const subscription = source.on(this.writeEvent.bind(this)); | |
|
|
20 | if (ct.isSupported()) { | |
|
|
21 | ct.register(subscription.destroy.bind(subscription)); | |
|
|
22 | } | |
|
|
23 | this._subscriptions.push(subscription); | |
|
|
24 | } | |
|
|
25 | ||
|
|
26 | writeEvent(next: TraceEvent) { | |
|
|
27 | // IE will create console only when devepoler tools are activated | |
|
|
28 | if (!hasConsole()) | |
|
|
29 | return; | |
|
|
30 | ||
|
|
31 | if (next.level >= DebugLevel) { | |
|
|
32 | // tslint:disable-next-line:no-console | |
|
|
33 | console.debug(next.source.id.toString(), next.arg); | |
|
|
34 | } else if (next.level >= LogLevel) { | |
|
|
35 | // tslint:disable-next-line:no-console | |
|
|
36 | console.log(next.source.id.toString(), next.arg); | |
|
|
37 | } else if (next.level >= WarnLevel) { | |
|
|
38 | // tslint:disable-next-line:no-console | |
|
|
39 | console.warn(next.source.id.toString(), next.arg); | |
|
|
40 | } else { | |
|
|
41 | // tslint:disable-next-line:no-console | |
|
|
42 | console.error(next.source.id.toString(), next.arg); | |
|
|
43 | } | |
|
|
44 | } | |
|
|
45 | ||
|
|
46 | destroy() { | |
|
|
47 | this._subscriptions.forEach(destroy); | |
|
|
48 | } | |
|
|
49 | } | |
|
|
1 | export { ConsoleLogger as ConsoleWriter } from "./ConsoleLogger"; No newline at end of file | |
| @@ -1,20 +1,41 | |||
|
|
1 | 1 | import { FormatScanner, TokeType } from "./FormatScanner"; |
|
|
2 | import { isNullOrEmptyString } from "../safe"; | |
|
|
3 | import { TextWriter } from "../interfaces"; | |
|
|
2 | import { isNullOrEmptyString, isPrimitive, get } from "../safe"; | |
|
|
3 | import { TextWriter, MapOf } from "../interfaces"; | |
|
|
4 | ||
|
|
5 | type CompiledPattern = (writer: TextWriter, args: any) => void; | |
|
|
4 | 6 | |
|
|
5 | 7 | export class FormatCompiler { |
|
|
6 | 8 | _scanner: FormatScanner; |
|
|
9 | _cache: MapOf<CompiledPattern> = {}; | |
|
|
7 | 10 | |
|
|
8 | _parts: []; | |
|
|
11 | _parts: Array<string | { name: string; format: string; }>; | |
|
|
12 | ||
|
|
13 | compile(pattern: string) { | |
|
|
14 | let compiledPattern = this._cache && this._cache[pattern]; | |
|
|
15 | if (!compiledPattern) { | |
|
|
16 | this._scanner = new FormatScanner(pattern); | |
|
|
17 | this._parts = []; | |
|
|
18 | ||
|
|
19 | this.visitText(); | |
|
|
20 | const parts = this._parts; | |
|
|
9 | 21 | |
|
|
10 | compile() { | |
|
|
11 | return (writer: TextWriter, args: any) => { | |
|
|
12 | this._parts.forEach(x => writer.WriteValue(x)) | |
|
|
13 | }; | |
|
|
22 | compiledPattern = (writer: TextWriter, args: any) => { | |
|
|
23 | parts.forEach(x => { | |
|
|
24 | if (isPrimitive(x)) | |
|
|
25 | writer.writeValue(x); | |
|
|
26 | else | |
|
|
27 | writer.writeValue(get(x.name, args), x.format); | |
|
|
28 | }); | |
|
|
29 | }; | |
|
|
30 | if (this._cache) | |
|
|
31 | this._cache[pattern] = compiledPattern; | |
|
|
32 | } | |
|
|
33 | return compiledPattern; | |
|
|
14 | 34 | } |
|
|
15 | 35 | |
|
|
16 | 36 | visitText() { |
|
|
17 | 37 | while (this._scanner.next()) { |
|
|
38 | // console.log(this._scanner.getTokenType(), this._scanner.getTokenValue()); | |
|
|
18 | 39 | switch (this._scanner.getTokenType()) { |
|
|
19 | 40 | case TokeType.CurlOpen: |
|
|
20 | 41 | this.visitCurlOpen(); |
| @@ -25,8 +46,6 export class FormatCompiler { | |||
|
|
25 | 46 | default: |
|
|
26 | 47 | this.pushText(this._scanner.getTokenValue()); |
|
|
27 | 48 | } |
|
|
28 | if (this._scanner.getTokenType() === TokeType.CurlOpen) | |
|
|
29 | this.visitCurlOpen(); | |
|
|
30 | 49 | } |
|
|
31 | 50 | } |
|
|
32 | 51 | |
| @@ -39,12 +58,14 export class FormatCompiler { | |||
|
|
39 | 58 | } |
|
|
40 | 59 | |
|
|
41 | 60 | visitCurlOpen() { |
|
|
42 |
if (this._scanner.next()) |
|
|
|
43 | if (this._scanner.getTokenType() === TokeType.CurlOpen) | |
|
|
44 | this.pushText("{"); | |
|
|
45 | else | |
|
|
46 |
|
|
|
|
47 |
|
|
|
|
61 | if (!this._scanner.next()) | |
|
|
62 | this.dieUnexpectedEnd("{ | TEXT"); | |
|
|
63 | ||
|
|
64 | if (this._scanner.getTokenType() === TokeType.CurlOpen) | |
|
|
65 | this.pushText("{"); | |
|
|
66 | else | |
|
|
67 | this.visitTemplateSubst(); | |
|
|
68 | ||
|
|
48 | 69 | } |
|
|
49 | 70 | |
|
|
50 | 71 | visitTemplateSubst() { |
| @@ -52,20 +73,23 export class FormatCompiler { | |||
|
|
52 | 73 | this.dieUnexpectedToken("TEXT"); |
|
|
53 | 74 | |
|
|
54 | 75 | const fieldName = this._scanner.getTokenValue(); |
|
|
55 |
const filedFormat = this.readColon() |
|
|
|
76 | const filedFormat = this.readColon() ? this.readFieldFormat() : null; | |
|
|
77 | ||
|
|
78 | if (this._scanner.getTokenType() !== TokeType.CurlClose) | |
|
|
79 | this.dieUnexpectedToken("}"); | |
|
|
56 | 80 | |
|
|
57 | 81 | this.pushSubst(fieldName, filedFormat); |
|
|
58 | 82 | } |
|
|
59 | 83 | |
|
|
60 | 84 | readFieldFormat() { |
|
|
61 | 85 | const parts = new Array<string>(); |
|
|
62 | while (this._scanner.next()) { | |
|
|
86 | do { | |
|
|
63 | 87 | if (this._scanner.getTokenType() === TokeType.CurlClose) { |
|
|
64 | 88 | return parts.join(""); |
|
|
65 | 89 | } else { |
|
|
66 | 90 | parts.push(this._scanner.getTokenValue()); |
|
|
67 | 91 | } |
|
|
68 | } | |
|
|
92 | } while (this._scanner.next()); | |
|
|
69 | 93 | |
|
|
70 | 94 | this.dieUnexpectedEnd("}"); |
|
|
71 | 95 | } |
| @@ -81,11 +105,12 export class FormatCompiler { | |||
|
|
81 | 105 | } |
|
|
82 | 106 | |
|
|
83 | 107 | pushSubst(fieldName: string, filedFormat: string) { |
|
|
84 | ||
|
|
108 | // console.log("pushSubst ", fieldName, filedFormat); | |
|
|
109 | this._parts.push({ name: fieldName, format: filedFormat }); | |
|
|
85 | 110 | } |
|
|
86 | 111 | |
|
|
87 | 112 | pushText(text: string) { |
|
|
88 | ||
|
|
113 | this._parts.push(text); | |
|
|
89 | 114 | } |
|
|
90 | 115 | |
|
|
91 | 116 | dieUnexpectedToken(expected?: string) { |
| @@ -2,10 +2,10 import { argumentNotEmptyString } from " | |||
|
|
2 | 2 | import { MapOf } from "../interfaces"; |
|
|
3 | 3 | |
|
|
4 | 4 | export const enum TokeType { |
|
|
5 | CurlOpen, | |
|
|
6 | CurlClose, | |
|
|
7 | Colon, | |
|
|
8 | Text | |
|
|
5 | CurlOpen = 1, | |
|
|
6 | CurlClose = 2, | |
|
|
7 | Colon = 3, | |
|
|
8 | Text = 4 | |
|
|
9 | 9 | } |
|
|
10 | 10 | |
|
|
11 | 11 | const typeMap = { |
| @@ -16,7 +16,6 const typeMap = { | |||
|
|
16 | 16 | |
|
|
17 | 17 | export class FormatScanner { |
|
|
18 | 18 | private _text: string; |
|
|
19 | private _pos: number; | |
|
|
20 | 19 | private _tokenType: TokeType; |
|
|
21 | 20 | private _tokenValue: string; |
|
|
22 | 21 | private _rx = /[^{}:]+|(.)/g; |
| @@ -29,7 +28,6 export class FormatScanner { | |||
|
|
29 | 28 | next() { |
|
|
30 | 29 | if (this._rx.lastIndex >= this._text.length) |
|
|
31 | 30 | return false; |
|
|
32 | this._pos = this._rx.lastIndex; | |
|
|
33 | 31 | |
|
|
34 | 32 | const match = this._rx.exec(this._text); |
|
|
35 | 33 | this._tokenType = typeMap[match[1]] || TokeType.Text; |
| @@ -1,18 +1,31 | |||
|
|
1 | export class StringBuilder { | |
|
|
2 | private _data: string[]; | |
|
|
3 | private _newLine = "\n"; | |
|
|
1 | import { TextWriterBase } from "./TextWriterBase"; | |
|
|
2 | import { Converter } from "./Converter"; | |
|
|
3 | ||
|
|
4 | export class StringBuilder extends TextWriterBase { | |
|
|
5 | private _data = new Array<string>(); | |
|
|
4 | 6 | |
|
|
5 | Write(obj: any); | |
|
|
6 | Write(format: string, ...args: any[]) { | |
|
|
7 | constructor(converter = Converter.default) { | |
|
|
8 | super(converter); | |
|
|
9 | } | |
|
|
7 | 10 | |
|
|
11 | writeText(text: string) { | |
|
|
12 | this._data.push(text); | |
|
|
8 | 13 | } |
|
|
9 | 14 | |
|
|
10 | WriteLine(obj: any); | |
|
|
11 | WriteLine(format: string, ...args: any[]) { | |
|
|
12 | ||
|
|
15 | toString() { | |
|
|
16 | return this._data.join(""); | |
|
|
13 | 17 | } |
|
|
14 | 18 | |
|
|
15 | WriteValue(value: any, spec?: string) { | |
|
|
16 | ||
|
|
19 | clear() { | |
|
|
20 | this._data.length = 0; | |
|
|
17 | 21 | } |
|
|
18 | 22 | } |
|
|
23 | ||
|
|
24 | const sb = new StringBuilder(); | |
|
|
25 | ||
|
|
26 | export function format(format: string, ...args: any): string; | |
|
|
27 | export function format() { | |
|
|
28 | sb.clear(); | |
|
|
29 | sb.write.apply(sb, arguments); | |
|
|
30 | return sb.toString(); | |
|
|
31 | } | |
| @@ -1,8 +1,8 | |||
|
|
1 | import * as tape from "tape"; | |
|
|
2 | 1 |
|
|
|
3 | 2 | import { SimpleActivatable } from "./mock/SimpleActivatable"; |
|
|
3 | import { test } from "./TestTraits"; | |
|
|
4 | 4 | |
|
|
5 |
t |
|
|
|
5 | test("simple activation", async t => { | |
|
|
6 | 6 | |
|
|
7 | 7 | const a = new SimpleActivatable(); |
|
|
8 | 8 | t.false(a.isActive()); |
| @@ -12,11 +12,9 tape("simple activation", async t => { | |||
|
|
12 | 12 | |
|
|
13 | 13 | await a.deactivate(); |
|
|
14 | 14 | t.false(a.isActive()); |
|
|
15 | ||
|
|
16 | t.end(); | |
|
|
17 | 15 | }); |
|
|
18 | 16 | |
|
|
19 |
t |
|
|
|
17 | test("controller activation", async t => { | |
|
|
20 | 18 | |
|
|
21 | 19 | const a = new SimpleActivatable(); |
|
|
22 | 20 | const c = new MockActivationController(); |
| @@ -37,11 +35,9 tape("controller activation", async t => | |||
|
|
37 | 35 | t.false(a.isActive(), "The component should successfully deactivate"); |
|
|
38 | 36 | t.equal(c.getActive(), null, "The controller shouldn't point to any component"); |
|
|
39 | 37 | t.equal(a.getActivationController(), c, "The componet should point to it's controller"); |
|
|
40 | ||
|
|
41 | t.end(); | |
|
|
42 | 38 | }); |
|
|
43 | 39 | |
|
|
44 |
t |
|
|
|
40 | test("handle error in onActivating", async t => { | |
|
|
45 | 41 | const a = new SimpleActivatable(); |
|
|
46 | 42 | |
|
|
47 | 43 | a.onActivating = async () => { |
| @@ -55,6 +51,4 tape("handle error in onActivating", asy | |||
|
|
55 | 51 | } |
|
|
56 | 52 | |
|
|
57 | 53 | t.false(a.isActive(), "the component should remain inactive"); |
|
|
58 | ||
|
|
59 | t.end(); | |
|
|
60 | 54 | }); |
| @@ -1,8 +1,8 | |||
|
|
1 | import * as tape from "tape"; | |
|
|
2 | 1 |
|
|
|
3 | 2 | import { delay } from "@implab/core/safe"; |
|
|
3 | import { test } from "./TestTraits"; | |
|
|
4 | 4 | |
|
|
5 |
t |
|
|
|
5 | test("standalone cancellation", async t => { | |
|
|
6 | 6 | |
|
|
7 | 7 | let doCancel: (e) => void; |
|
|
8 | 8 | |
| @@ -43,11 +43,9 tape("standalone cancellation", async t | |||
|
|
43 | 43 | msg = e; |
|
|
44 | 44 | } |
|
|
45 | 45 | t.equals(msg, reason, "The cancellation reason should be catched"); |
|
|
46 | ||
|
|
47 | t.end(); | |
|
|
48 | 46 | }); |
|
|
49 | 47 | |
|
|
50 |
t |
|
|
|
48 | test("async cancellation", async t => { | |
|
|
51 | 49 | |
|
|
52 | 50 | const ct = new Cancellation(cancel => { |
|
|
53 | 51 | cancel("STOP!"); |
| @@ -59,11 +57,9 tape("async cancellation", async t => { | |||
|
|
59 | 57 | } catch (e) { |
|
|
60 | 58 | t.equals(e, "STOP!", "Should throw the cancellation reason"); |
|
|
61 | 59 | } |
|
|
62 | ||
|
|
63 | t.end(); | |
|
|
64 | 60 | }); |
|
|
65 | 61 | |
|
|
66 |
t |
|
|
|
62 | test("cancel with external event", async t => { | |
|
|
67 | 63 | const ct = new Cancellation(cancel => { |
|
|
68 | 64 | setTimeout(x => cancel("STOP!"), 0); |
|
|
69 | 65 | }); |
| @@ -74,11 +70,9 tape("cancel with external event", async | |||
|
|
74 | 70 | } catch (e) { |
|
|
75 | 71 | t.equals(e, "STOP!", "Should throw the cancellation reason"); |
|
|
76 | 72 | } |
|
|
77 | ||
|
|
78 | t.end(); | |
|
|
79 | 73 | }); |
|
|
80 | 74 | |
|
|
81 |
t |
|
|
|
75 | test("operation normal flow", async t => { | |
|
|
82 | 76 | |
|
|
83 | 77 | let htimeout; |
|
|
84 | 78 | const ct = new Cancellation(cancel => { |
| @@ -91,6 +85,4 tape("operation normal flow", async t => | |||
|
|
91 | 85 | } finally { |
|
|
92 | 86 | clearTimeout(htimeout); |
|
|
93 | 87 | } |
|
|
94 | ||
|
|
95 | t.end(); | |
|
|
96 | 88 | }); |
| @@ -1,12 +1,12 | |||
|
|
1 | 1 | import { TraceSource, DebugLevel } from "@implab/core/log/TraceSource"; |
|
|
2 | import * as tape from "tape"; | |
|
|
3 | 2 | import { Observable } from "@implab/core/Observable"; |
|
|
4 | 3 | import { IObservable } from "@implab/core/interfaces"; |
|
|
5 | 4 | import { delay } from "@implab/core/safe"; |
|
|
5 | import { test } from "./TestTraits"; | |
|
|
6 | 6 | |
|
|
7 | 7 | const trace = TraceSource.get("ObservableTests"); |
|
|
8 | 8 | |
|
|
9 |
t |
|
|
|
9 | test("events sequence example", async t => { | |
|
|
10 | 10 | |
|
|
11 | 11 | let events: IObservable<number>; |
|
|
12 | 12 | |
| @@ -34,11 +34,9 tape("events sequence example", async t | |||
|
|
34 | 34 | |
|
|
35 | 35 | t.equals(count, 45, "the summ of the evetns"); |
|
|
36 | 36 | t.true(complete, "the sequence is complete"); |
|
|
37 | ||
|
|
38 | t.end(); | |
|
|
39 | 37 | }); |
|
|
40 | 38 | |
|
|
41 |
t |
|
|
|
39 | test("event sequence termination", async t => { | |
|
|
42 | 40 | let events: IObservable<number>; |
|
|
43 | 41 | |
|
|
44 | 42 | const done = new Promise<void>(resolve => { |
| @@ -68,6 +66,4 tape("event sequence termination", async | |||
|
|
68 | 66 | await done; |
|
|
69 | 67 | |
|
|
70 | 68 | t.equals(count, 1, "the sequence must be terminated once"); |
|
|
71 | ||
|
|
72 | t.end(); | |
|
|
73 | 69 | }); |
| @@ -1,8 +1,8 | |||
|
|
1 | import tape = require("tape"); | |
|
|
2 | 1 |
|
|
|
3 | 2 | import { first, isPromise, firstWhere, delay, nowait } from "@implab/core/safe"; |
|
|
3 | import { test } from "./TestTraits"; | |
|
|
4 | 4 | |
|
|
5 |
t |
|
|
|
5 | test("await delay test", async t => { | |
|
|
6 | 6 | // schedule delay |
|
|
7 | 7 | let resolved = false; |
|
|
8 | 8 | let res = delay(0).then(() => resolved = true); |
| @@ -36,11 +36,9 tape("await delay test", async t => { | |||
|
|
36 | 36 | // try schedule delay after the cancellation is requested |
|
|
37 | 37 | nowait(delay(0, ct)); |
|
|
38 | 38 | }, "Should throw if cancelled before start"); |
|
|
39 | ||
|
|
40 | t.end(); | |
|
|
41 | 39 | }); |
|
|
42 | 40 | |
|
|
43 |
t |
|
|
|
41 | test("sequemce test", async t => { | |
|
|
44 | 42 | const sequence = ["a", "b", "c"]; |
|
|
45 | 43 | const empty = []; |
|
|
46 | 44 | |
| @@ -94,6 +92,4 tape("sequemce test", async t => { | |||
|
|
94 | 92 | |
|
|
95 | 93 | v = await new Promise(resolve => first(asyncSequence, resolve)); |
|
|
96 | 94 | t.equal(v, "a", "The callback should be called for the first element"); |
|
|
97 | ||
|
|
98 | t.end(); | |
|
|
99 | 95 | }); |
| @@ -5,9 +5,10 import * as tape from "tape"; | |||
|
|
5 | 5 | import { argumentNotNull, destroy } from "@implab/core/safe"; |
|
|
6 | 6 | |
|
|
7 | 7 | export class TapeWriter implements IDestroyable { |
|
|
8 | readonly _tape: tape.Test; | |
|
|
8 | private readonly _tape: tape.Test; | |
|
|
9 | 9 | |
|
|
10 | _subscriptions = new Array<IDestroyable>(); | |
|
|
10 | private readonly _subscriptions = new Array<IDestroyable>(); | |
|
|
11 | private _destroyed; | |
|
|
11 | 12 | |
|
|
12 | 13 | constructor(t: tape.Test) { |
|
|
13 | 14 | argumentNotNull(t, "tape"); |
| @@ -15,22 +16,24 export class TapeWriter implements IDest | |||
|
|
15 | 16 | } |
|
|
16 | 17 | |
|
|
17 | 18 | writeEvents(source: IObservable<TraceEvent>, ct: ICancellation = Cancellation.none) { |
|
|
18 | const subscription = source.on(this.writeEvent.bind(this)); | |
|
|
19 | if (ct.isSupported()) { | |
|
|
20 | ct.register(subscription.destroy.bind(subscription)); | |
|
|
19 | if (!this._destroyed) { | |
|
|
20 | const subscription = source.on(this.writeEvent.bind(this)); | |
|
|
21 | if (ct.isSupported()) { | |
|
|
22 | ct.register(subscription.destroy.bind(subscription)); | |
|
|
23 | } | |
|
|
24 | this._subscriptions.push(subscription); | |
|
|
21 | 25 | } |
|
|
22 | this._subscriptions.push(subscription); | |
|
|
23 | 26 | } |
|
|
24 | 27 | |
|
|
25 | 28 | writeEvent(next: TraceEvent) { |
|
|
26 | 29 | if (next.level >= DebugLevel) { |
|
|
27 |
this._tape.comment(`DEBUG ${next.source.id} ${next |
|
|
|
30 | this._tape.comment(`DEBUG ${next.source.id} ${next}`); | |
|
|
28 | 31 | } else if (next.level >= LogLevel) { |
|
|
29 |
this._tape.comment(`LOG ${next.source.id} ${next |
|
|
|
32 | this._tape.comment(`LOG ${next.source.id} ${next}`); | |
|
|
30 | 33 | } else if (next.level >= WarnLevel) { |
|
|
31 |
this._tape.comment(`WARN ${next.source.id} ${next |
|
|
|
34 | this._tape.comment(`WARN ${next.source.id} ${next}`); | |
|
|
32 | 35 | } else { |
|
|
33 |
this._tape.comment(`ERROR ${next.source.id} ${next |
|
|
|
36 | this._tape.comment(`ERROR ${next.source.id} ${next}`); | |
|
|
34 | 37 | } |
|
|
35 | 38 | } |
|
|
36 | 39 | |
| @@ -39,17 +42,22 export class TapeWriter implements IDest | |||
|
|
39 | 42 | } |
|
|
40 | 43 | } |
|
|
41 | 44 | |
|
|
42 | export function test(name: string, cb: (t: tape.Test) => any) { | |
|
|
45 | export function test(name: string, cb: (t: tape.Test, trace: TraceSource) => any) { | |
|
|
43 | 46 | tape(name, async t => { |
|
|
44 | 47 | const writer = new TapeWriter(t); |
|
|
45 | 48 | |
|
|
46 | TraceSource.on(ts => { | |
|
|
49 | // this trace is not announced through the TraceSource global registry | |
|
|
50 | const trace = new TraceSource(name); | |
|
|
51 | trace.level = DebugLevel; | |
|
|
52 | writer.writeEvents(trace.events); | |
|
|
53 | ||
|
|
54 | const h = TraceSource.on(ts => { | |
|
|
47 | 55 | ts.level = DebugLevel; |
|
|
48 | 56 | writer.writeEvents(ts.events); |
|
|
49 | 57 | }); |
|
|
50 | 58 | |
|
|
51 | 59 | try { |
|
|
52 | await cb(t); | |
|
|
60 | await cb(t, trace); | |
|
|
53 | 61 | } catch (e) { |
|
|
54 | 62 | |
|
|
55 | 63 | // verbose error information |
| @@ -60,6 +68,7 export function test(name: string, cb: ( | |||
|
|
60 | 68 | } finally { |
|
|
61 | 69 | t.end(); |
|
|
62 | 70 | destroy(writer); |
|
|
71 | destroy(h); | |
|
|
63 | 72 | } |
|
|
64 | 73 | }); |
|
|
65 | 74 | } |
| @@ -1,6 +1,9 | |||
|
|
1 | 1 | import { TraceSource, DebugLevel } from "@implab/core/log/TraceSource"; |
|
|
2 | 2 | import * as tape from "tape"; |
|
|
3 | import { TapeWriter } from "./TestTraits"; | |
|
|
3 | import { TapeWriter, test } from "./TestTraits"; | |
|
|
4 | import { MockConsole } from "./mock/MockConsole"; | |
|
|
5 | import { ConsoleLog } from "@implab/core/log/writers/ConsoleLog"; | |
|
|
6 | import { ConsoleWriter } from "@implab/core/log/ConsoleWriter"; | |
|
|
4 | 7 | |
|
|
5 | 8 | const sourceId = "test/TraceSourceTests"; |
|
|
6 | 9 | |
| @@ -12,7 +15,7 tape("trace message", t => { | |||
|
|
12 | 15 | const h = trace.events.on(ev => { |
|
|
13 | 16 | t.equal(ev.source, trace, "sender should be the current trace source"); |
|
|
14 | 17 | t.equal(ev.level, DebugLevel, "level should be debug level"); |
|
|
15 |
t.equal(ev. |
|
|
|
18 | t.equal(ev.toString(), "Hello, World!", "The message should be a formatted message"); | |
|
|
16 | 19 | |
|
|
17 | 20 | t.end(); |
|
|
18 | 21 | }); |
| @@ -34,7 +37,7 tape("trace event", t => { | |||
|
|
34 | 37 | const h = trace.events.on(ev => { |
|
|
35 | 38 | t.equal(ev.source, trace, "sender should be the current trace source"); |
|
|
36 | 39 | t.equal(ev.level, DebugLevel, "level should be debug level"); |
|
|
37 |
t.equal(ev.a |
|
|
|
40 | t.equal(ev.message, event, "The message should be the specified object"); | |
|
|
38 | 41 | |
|
|
39 | 42 | t.end(); |
|
|
40 | 43 | }); |
| @@ -67,3 +70,17 tape("tape comment writer", async t => { | |||
|
|
67 | 70 | |
|
|
68 | 71 | t.end(); |
|
|
69 | 72 | }); |
|
|
73 | ||
|
|
74 | test("console writer", (t, trace) => { | |
|
|
75 | ||
|
|
76 | const mockConsole = new MockConsole(); | |
|
|
77 | const writer = new ConsoleWriter(mockConsole); | |
|
|
78 | const consoleLog = new ConsoleLog(writer); | |
|
|
79 | consoleLog.writeEvents(trace.events); | |
|
|
80 | ||
|
|
81 | trace.log("Hello, world!"); | |
|
|
82 | t.deepEqual(mockConsole.getLine(0), ["console writer: Hello, world!"], "Log one string"); | |
|
|
83 | ||
|
|
84 | trace.log({ foo: "bar" }); | |
|
|
85 | t.deepEqual(mockConsole.getLine(1), ["console writer: ", { foo: "bar" }], "Log an object"); | |
|
|
86 | }); | |
General Comments 0
You need to be logged in to leave comments.
Login now
