diff --git a/src/LogLevel.ts b/src/LogLevel.ts index 1c97d02..e1a88d4 100644 --- a/src/LogLevel.ts +++ b/src/LogLevel.ts @@ -1,7 +1,30 @@ +import { padNum } from "./padNum"; import { padStr } from "./padStr"; +const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; +const months = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec" +]; +const ordSuffix = ["st", "nd", "rd"]; + type ColorFormatter = (str: string) => string; +interface MessageTemplate { + template: string; + utc?: boolean; +} + interface LogLevelOpts { /** * Name of the log level @@ -25,12 +48,29 @@ interface LogLevelOpts { color?: ColorFormatter; } +function toTemplateObjs( + msgVal: string | MessageTemplate, + fileMsgVal?: string | MessageTemplate +) { + const msgTemplate: MessageTemplate = + typeof msgVal == "string" + ? { template: msgVal, utc: false } + : { ...msgVal }; + + const fileMsgTemplate: MessageTemplate = fileMsgVal + ? typeof fileMsgVal == "string" + ? { template: fileMsgVal, utc: false } + : { ...fileMsgVal } + : msgTemplate; + return { msg: msgTemplate, file: fileMsgTemplate }; +} + export class LogLevel { #name: string; - #msgTemplate: string; - #fileMsgTemplate: string; + #templates: ReturnType; #padLen: number; #color?: ColorFormatter; + #now: Date; constructor({ name, @@ -40,20 +80,87 @@ export class LogLevel { color }: LogLevelOpts) { this.#name = name; - this.#msgTemplate = msgTemplate; - this.#fileMsgTemplate = fileMsgTemplate || msgTemplate; + this.#templates = toTemplateObjs(msgTemplate, fileMsgTemplate); this.#padLen = padLen || 0; this.#color = color; + this.#now = new Date(); } - #processTemplateVar(str: string /* , now: Date*/) { - switch (str) { - case "%name%": - return this.#name.toLowerCase(); - case "%NAME%": - return this.#name.toUpperCase(); - case "%Name%": - return this.#name; + #processTemplateVar(str: string, utc: boolean): string { + const now = this.#now; + const r = (str: string) => this.#processTemplateVar(`%${str}%`, utc); // call this method recursively + + switch (str.toLowerCase()) { + case "%year%": + case "%#year%": + return now[utc ? "getUTCFullYear" : "getFullYear"]().toString(); + + case "%month%": + return (now[utc ? "getUTCMonth" : "getMonth"]() + 1).toString(); + case "%#month%": + return padNum(r("month"), 2); + + case "%date%": + return now[utc ? "getUTCDate" : "getDate"]().toString(); + case "%#date%": + return padNum(r("date"), 2); + + case "%hour%": + case "%hours%": + return now[utc ? "getUTCHours" : "getHours"]().toString(); + + case "%#hour%": + case "%#hours%": + return padNum(r("hour"), 2); + + case "%min%": + case "%mins%": + case "%minute%": + case "%minutes%": + return now[utc ? "getUTCMinutes" : "getMinutes"]().toString(); + + case "%#min%": + case "%#mins%": + case "%#minute%": + case "%#minutes%": + return padNum(r("min"), 2); + + case "%sec%": + case "%second%": + case "%seconds%": + return now[utc ? "getUTCSeconds" : "getSeconds"]().toString(); + + case "%#sec%": + case "%#second%": + case "%#seconds%": + return padNum(r("sec"), 2); + + case "%day%": + return weekdays[now[utc ? "getUTCDay" : "getDay"]()]; + + case "%month_str%": + return months[now[utc ? "getUTCMonth" : "getMonth"]()]; + + case "%date_ord%": { + const date = now[utc ? "getUTCDate" : "getDate"](); + const suffix = + (4 <= date && date <= 20) || (24 <= date && date <= 30) + ? "th" + : ordSuffix[(date % 10) - 1]; + return date + suffix; + } + + case "%iso%": + case "%iso_short%": + return `${r("year")}-${r("#month")}-${r("#date")}`; + + case "%iso_full%": + case "%iso_long%": + return now.toISOString(); + + case "%time%": + return `${r("#hour")}:${r("#min")}:${r("#sec")}`; + default: return str; } @@ -65,8 +172,9 @@ export class LogLevel { let val = this.#name; if (str.startsWith("%#")) direction = "left"; - if (str.endsWith("#%")) + if (str.endsWith("#%")) { direction = direction == "left" ? "center" : "right"; + } switch (tag) { case "name": @@ -82,17 +190,20 @@ export class LogLevel { return direction ? padStr(val, this.#padLen, direction) : val; } - #processPrefix(template: string, isFileLog?: boolean) { - //const now = new Date(); + #processPrefix({ template, utc }: MessageTemplate, isFileLog?: boolean) { + this.#now = new Date(); + return template .replace(/%#?(?:name|Name|NAME)#?%/g, str => this.#processNameVar(str, isFileLog) ) - .replace(/%[a-z]+%/gi, str => this.#processTemplateVar(str /*, now*/)); + .replace(/%#?[a-z_]+%/gi, str => + this.#processTemplateVar(str, utc ?? false) + ); } processTemplate(msg: string, isFileLog?: boolean) { - const template = isFileLog ? this.#fileMsgTemplate : this.#msgTemplate; + const template = this.#templates[isFileLog ? "file" : "msg"]; const prefix = this.#processPrefix(template, isFileLog); return `${prefix} ${msg}`; } diff --git a/src/padNum.ts b/src/padNum.ts new file mode 100644 index 0000000..5c6e919 --- /dev/null +++ b/src/padNum.ts @@ -0,0 +1,3 @@ +export function padNum(num: number | string, len: number) { + return num.toString().padStart(len, "0"); +} diff --git a/tests/LogLevel.test.ts b/tests/LogLevel.test.ts index 09951c5..b150417 100644 --- a/tests/LogLevel.test.ts +++ b/tests/LogLevel.test.ts @@ -70,6 +70,19 @@ test("processTemplate - Does not transform unknown variables", () => { assert.is(level.processTemplate("foo"), "%wrong% test: foo"); }); +test("processTemplate - Dates", () => { + const level = new LogLevel({ + name: "test", + msgTemplate: { template: "[%iso% %time%] %name% -", utc: true } + }); + assert.ok( + /^\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}] test -/.test( + level.processTemplate("foo") + ), + "test template with %iso% and %time% using regex" + ); +}); + test("processTemplate - name colour", () => { const level = new LogLevel({ name: "test",