Skip to content

Commit 382e67f

Browse files
committed
sketch: initial design
1 parent 3dcdf22 commit 382e67f

File tree

10 files changed

+209
-8
lines changed

10 files changed

+209
-8
lines changed

s/example.test.ts

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,52 @@
11

2-
import {Science, test, expect} from "@e280/science"
32
import {Logger} from "./logger.js"
3+
import {MockTarget} from "./targets/mock.js"
4+
import {Science, test, expect} from "@e280/science"
45

56
await Science.run({
6-
"addition works": test(async() => {
7-
expect(2 + 2).is(4)
8-
// const logger = new Logger()
9-
// .target(Logger.node)
10-
// .palette(Logger.colorful)
11-
// .transform(Logger.timestamp)
7+
"basics": Science.suite({
8+
"create a logger": test(async() => {
9+
void new Logger()
10+
}),
11+
"logger with mock": test(async() => {
12+
const mock = new MockTarget()
13+
void new Logger().setTarget(mock)
14+
}),
15+
"logger stdout": test(async() => {
16+
const mock = new MockTarget()
17+
const logger = new Logger().setTarget(mock)
18+
expect(mock.stdout.spy.calls.length).is(0)
19+
logger.log("hello world!")
20+
expect(mock.stdout.spy.calls.length).is(1)
21+
expect(mock.getSpyStdout(0)).is("hello world!")
22+
expect(mock.stderr.spy.calls.length).is(0)
23+
}),
24+
"logger stderr": test(async() => {
25+
const mock = new MockTarget()
26+
const logger = new Logger().setTarget(mock)
27+
expect(mock.stderr.spy.calls.length).is(0)
28+
logger.error("hello world!")
29+
expect(mock.stderr.spy.calls.length).is(1)
30+
expect(mock.getSpyStderr(0)).is("hello world!")
31+
expect(mock.stdout.spy.calls.length).is(0)
32+
}),
33+
}),
34+
35+
"transforms": Science.suite({
36+
"custom prefix": test(async() => {
37+
const mock = new MockTarget()
38+
const logger = new Logger()
39+
.setTarget(mock)
40+
.setPalette(Logger.palettes.plain())
41+
.addTransform(() => ({
42+
stdout: items => ["stdout:", ...items],
43+
stderr: items => ["stderr:", ...items],
44+
}))
45+
logger.log("hello world!")
46+
console.log(mock.getSpyStdout(0))
47+
expect(mock.stdout.spy.calls.length).is(1)
48+
expect(mock.getSpyStdout(0)).is("stdout: hello world!")
49+
}),
1250
}),
1351
})
1452

s/logger.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,55 @@
11

2-
export class Logger {}
2+
import {Target} from "./targets/target.js"
3+
import {nodeTarget} from "./targets/node.js"
4+
import {Palette} from "./palettes/palette.js"
5+
import {plainPalette} from "./palettes/plain.js"
6+
import {consoleTarget} from "./targets/console.js"
7+
import {Transformer} from "./transforms/transform.js"
8+
import {timestampTransform} from "./transforms/timestamp.js"
9+
10+
export class Logger {
11+
static palettes = {
12+
plain: plainPalette,
13+
}
14+
15+
static targets = {
16+
node: nodeTarget,
17+
console: consoleTarget,
18+
}
19+
20+
static transforms = {
21+
timestamp: timestampTransform,
22+
}
23+
24+
palette: Palette = {}
25+
target = consoleTarget()
26+
transformers: Transformer[] = []
27+
28+
log(...items: any[]) {
29+
for (const transform of this.transformers)
30+
items = transform(this).stdout(items)
31+
this.target.stdout(items)
32+
}
33+
34+
error(...items: any[]) {
35+
for (const transform of this.transformers)
36+
items = transform(this).stderr(items)
37+
this.target.stderr(items)
38+
}
39+
40+
setPalette(palette: Palette) {
41+
this.palette = palette
42+
return this
43+
}
44+
45+
setTarget(target: Target) {
46+
this.target = target
47+
return this
48+
}
49+
50+
addTransform(transformer: Transformer) {
51+
this.transformers.push(transformer)
52+
return this
53+
}
54+
}
355

s/palettes/palette.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
export type Palette = {}
3+

s/palettes/plain.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
import {Palette} from "./palette.js"
3+
4+
export const plainPalette = (): Palette => ({})
5+

s/targets/console.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
import {Target} from "./target.js"
3+
4+
export const consoleTarget = (): Target => ({
5+
stdout: (items: any[]) => console.log(...items),
6+
stderr: (items: any[]) => console.error(...items),
7+
})
8+

s/targets/mock.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
2+
import {Target} from "./target.js"
3+
4+
export function spy<Fn extends (...args: any[]) => any>(fn: Fn) {
5+
const calls: {args: Parameters<Fn>, ret: ReturnType<Fn>}[] = []
6+
7+
function spyFn(...args: Parameters<Fn>) {
8+
const ret = fn()
9+
calls.push({args, ret})
10+
return ret
11+
}
12+
13+
spyFn.spy = {
14+
calls,
15+
get args() {
16+
return calls.map(c => c.args)
17+
},
18+
get rets() {
19+
return calls.map(c => c.ret)
20+
},
21+
}
22+
23+
return spyFn
24+
}
25+
26+
export class MockTarget implements Target {
27+
stdout = spy((_items: any[]) => {})
28+
stderr = spy((_items: any[]) => {})
29+
30+
getSpyStdout(index: number) {
31+
const [items] = this.stdout.spy.args.at(index)!
32+
return items.join(" ")
33+
}
34+
35+
getSpyStderr(index: number) {
36+
const [items] = this.stderr.spy.args.at(index)!
37+
return items.join(" ")
38+
}
39+
}
40+

s/targets/node.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
import {Target} from "./target.js"
3+
4+
export const nodeTarget = (): Target => ({
5+
stdout: (items: any[]) => void process.stdout.write(items.join(" ")),
6+
stderr: (items: any[]) => void process.stderr.write(items.join(" ")),
7+
})
8+

s/targets/target.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
export type Target = {
3+
stdout(items: any[]): void
4+
stderr(items: any[]): void
5+
}
6+

s/transforms/timestamp.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
2+
import {Transformer} from "./transform.js"
3+
4+
export type TimestampOptions = {
5+
now: () => number
6+
}
7+
8+
function defaultTimestampOptions(): TimestampOptions {
9+
return {
10+
now: () => Date.now(),
11+
}
12+
}
13+
14+
export const timestampTransform = (options?: Partial<TimestampOptions>): Transformer => ({palette}) => {
15+
const opts = {...defaultTimestampOptions(), ...options}
16+
return {
17+
stdout: items => [
18+
`[${Math.round(opts.now())}]`,
19+
...items,
20+
],
21+
stderr: items => [
22+
`[${Math.round(opts.now())}]`,
23+
...items,
24+
],
25+
}
26+
}
27+

s/transforms/transform.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
import {Palette} from "../palettes/palette.js"
3+
4+
export type TransformerContext = {
5+
palette: Palette
6+
}
7+
8+
export type Transformer = (context: TransformerContext) => Transform
9+
10+
export type Transform = {
11+
stdout: (items: any[]) => any[]
12+
stderr: (items: any[]) => any[]
13+
}
14+

0 commit comments

Comments
 (0)