@peteanderson/ansi 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +194 -0
  3. package/dist/ansi.d.ts +79 -41
  4. package/dist/ansi.d.ts.map +1 -1
  5. package/dist/ansi.js +9 -6
  6. package/dist/ansi.js.map +1 -1
  7. package/dist/features/build.d.ts.map +1 -1
  8. package/dist/features/build.js.map +1 -1
  9. package/dist/features/detect.d.ts.map +1 -1
  10. package/dist/features/detect.js.map +1 -1
  11. package/dist/features/features.d.ts +1 -3
  12. package/dist/features/features.d.ts.map +1 -1
  13. package/dist/features/features.js +12 -13
  14. package/dist/features/features.js.map +1 -1
  15. package/dist/features/index.d.ts +1 -0
  16. package/dist/features/index.d.ts.map +1 -1
  17. package/dist/features/types.d.ts +2 -0
  18. package/dist/features/types.d.ts.map +1 -1
  19. package/dist/pad.d.ts +10 -0
  20. package/dist/pad.d.ts.map +1 -1
  21. package/dist/pad.js +8 -0
  22. package/dist/pad.js.map +1 -1
  23. package/dist/sgr/color/channel.d.ts +4 -0
  24. package/dist/sgr/color/channel.d.ts.map +1 -0
  25. package/dist/sgr/color/channel.js +33 -0
  26. package/dist/sgr/color/channel.js.map +1 -0
  27. package/dist/sgr/color/color.d.ts +3 -4
  28. package/dist/sgr/color/color.d.ts.map +1 -1
  29. package/dist/sgr/color/color.js +16 -65
  30. package/dist/sgr/color/color.js.map +1 -1
  31. package/dist/sgr/color/context.d.ts +13 -0
  32. package/dist/sgr/color/context.d.ts.map +1 -0
  33. package/dist/sgr/color/context.js +24 -0
  34. package/dist/sgr/color/context.js.map +1 -0
  35. package/dist/sgr/color/rgb.d.ts +5 -0
  36. package/dist/sgr/color/rgb.d.ts.map +1 -0
  37. package/dist/sgr/color/rgb.js +32 -0
  38. package/dist/sgr/color/rgb.js.map +1 -0
  39. package/dist/sgr/color/utils.d.ts.map +1 -1
  40. package/dist/sgr/color/utils.js +12 -4
  41. package/dist/sgr/color/utils.js.map +1 -1
  42. package/dist/sgr/combine.d.ts +3 -0
  43. package/dist/sgr/combine.d.ts.map +1 -0
  44. package/dist/sgr/combine.js +14 -0
  45. package/dist/sgr/combine.js.map +1 -0
  46. package/dist/sgr/format-builder.d.ts +3 -0
  47. package/dist/sgr/format-builder.d.ts.map +1 -0
  48. package/dist/sgr/format-builder.js +33 -0
  49. package/dist/sgr/format-builder.js.map +1 -0
  50. package/dist/sgr/index.d.ts +2 -3
  51. package/dist/sgr/index.d.ts.map +1 -1
  52. package/dist/sgr/index.js +1 -3
  53. package/dist/sgr/index.js.map +1 -1
  54. package/dist/sgr/lazy.d.ts +6 -0
  55. package/dist/sgr/lazy.d.ts.map +1 -0
  56. package/dist/sgr/lazy.js +17 -0
  57. package/dist/sgr/lazy.js.map +1 -0
  58. package/dist/sgr/sgr.d.ts +3 -14
  59. package/dist/sgr/sgr.d.ts.map +1 -1
  60. package/dist/sgr/sgr.js +44 -37
  61. package/dist/sgr/sgr.js.map +1 -1
  62. package/dist/sgr/style.d.ts +2 -14
  63. package/dist/sgr/style.d.ts.map +1 -1
  64. package/dist/sgr/style.js +27 -16
  65. package/dist/sgr/style.js.map +1 -1
  66. package/dist/sgr/types.d.ts +59 -0
  67. package/dist/sgr/types.d.ts.map +1 -0
  68. package/dist/sgr/{color/types.js.map → types.js.map} +1 -1
  69. package/dist/simplify/atom.d.ts.map +1 -1
  70. package/dist/simplify/atom.js +8 -1
  71. package/dist/simplify/atom.js.map +1 -1
  72. package/dist/simplify/simplify.d.ts +1 -0
  73. package/dist/simplify/simplify.d.ts.map +1 -1
  74. package/dist/slice.d.ts +1 -0
  75. package/dist/slice.d.ts.map +1 -1
  76. package/dist/slice.js +1 -0
  77. package/dist/slice.js.map +1 -1
  78. package/dist/strip.d.ts +6 -0
  79. package/dist/strip.d.ts.map +1 -1
  80. package/dist/strip.js +3 -0
  81. package/dist/strip.js.map +1 -1
  82. package/dist/typed-object.d.ts +16 -0
  83. package/dist/typed-object.d.ts.map +1 -0
  84. package/dist/typed-object.js +36 -0
  85. package/dist/typed-object.js.map +1 -0
  86. package/dist/types.d.ts +25 -0
  87. package/dist/types.d.ts.map +1 -0
  88. package/dist/types.js +3 -0
  89. package/dist/types.js.map +1 -0
  90. package/package.json +26 -10
  91. package/src/ansi.ts +11 -36
  92. package/src/features/build.ts +4 -3
  93. package/src/features/detect.ts +6 -3
  94. package/src/features/features.ts +19 -30
  95. package/src/features/index.ts +2 -0
  96. package/src/features/types.ts +9 -0
  97. package/src/pad.ts +12 -1
  98. package/src/sgr/color/channel.ts +44 -0
  99. package/src/sgr/color/color.ts +30 -95
  100. package/src/sgr/color/context.ts +44 -0
  101. package/src/sgr/color/index.ts +1 -1
  102. package/src/sgr/color/rgb.ts +48 -0
  103. package/src/sgr/color/utils.ts +13 -4
  104. package/src/sgr/combine.ts +13 -0
  105. package/src/sgr/format-builder.ts +47 -0
  106. package/src/sgr/index.ts +3 -3
  107. package/src/sgr/lazy.ts +21 -0
  108. package/src/sgr/sgr.ts +61 -57
  109. package/src/sgr/style.ts +35 -18
  110. package/src/sgr/types.ts +91 -0
  111. package/src/simplify/atom.ts +8 -2
  112. package/src/simplify/simplify.ts +3 -1
  113. package/src/slice.ts +4 -1
  114. package/src/strip.ts +10 -1
  115. package/src/typed-object.ts +85 -0
  116. package/src/types.ts +28 -0
  117. package/src/utility-types.d.ts +18 -0
  118. package/dist/sgr/color/test-rgb.d.ts +0 -3
  119. package/dist/sgr/color/test-rgb.d.ts.map +0 -1
  120. package/dist/sgr/color/test-rgb.js +0 -149
  121. package/dist/sgr/color/test-rgb.js.map +0 -1
  122. package/dist/sgr/color/types.d.ts +0 -30
  123. package/dist/sgr/color/types.d.ts.map +0 -1
  124. package/dist/sgr/utils.d.ts +0 -3
  125. package/dist/sgr/utils.d.ts.map +0 -1
  126. package/dist/sgr/utils.js +0 -16
  127. package/dist/sgr/utils.js.map +0 -1
  128. package/dist/simplify/test.d.ts +0 -2
  129. package/dist/simplify/test.d.ts.map +0 -1
  130. package/dist/simplify/test.js +0 -64
  131. package/dist/simplify/test.js.map +0 -1
  132. package/src/sgr/color/types.ts +0 -32
  133. package/src/sgr/utils.ts +0 -17
  134. /package/dist/sgr/{color/types.js → types.js} +0 -0
@@ -0,0 +1,44 @@
1
+ import {lazy} from "../lazy";
2
+ import {buildContext} from "./context";
3
+ import {makeRGB, makeX256} from "./rgb";
4
+
5
+ import type {ColorDepth} from "../../features";
6
+ import type {ChainKey, Channel, ColorKey, FormatBuilder, Style} from "../types";
7
+
8
+ const colors = [
9
+ "black",
10
+ "red",
11
+ "green",
12
+ "yellow",
13
+ "blue",
14
+ "magenta",
15
+ "cyan",
16
+ "white",
17
+ ] as const;
18
+
19
+ export function makeChannel(
20
+ keys : ReadonlySet<ChainKey>,
21
+ makeFormatter : FormatBuilder,
22
+ style : Style,
23
+ colorDepth : ColorDepth,
24
+ channel : ColorKey,
25
+ ): Channel {
26
+ const ctx = buildContext(keys, makeFormatter, style, colorDepth, channel);
27
+ const {build, bright, open, close} = ctx;
28
+
29
+ const rtn = {
30
+ rgb : makeRGB(ctx),
31
+ x256 : makeX256(ctx),
32
+ default : colorDepth > 1 ? `\x1b[${close}m` : "",
33
+ } as Channel;
34
+
35
+ for (let i = 0; i < colors.length; i++) {
36
+ const color = colors[i];
37
+ const brightColor = `bright${color[0].toUpperCase()}${color.slice(1)}`;
38
+
39
+ lazy.add(rtn, color, () => build(open + i));
40
+ lazy.add(rtn, brightColor, () => bright(i));
41
+ }
42
+
43
+ return rtn;
44
+ }
@@ -1,95 +1,30 @@
1
- import {makeSGR} from "../sgr";
2
- import {makeStyle} from "../style";
3
- import {clip, rgbToX16, rgbToX256, rgbToX8, x256ToRgb} from "./utils";
4
-
5
- import type {ColorDepth} from "../../features/types";
6
- import type {SGR} from "../sgr";
7
- import type {Channel, Color} from "./types";
8
-
9
- export type {Color} from "./types";
10
-
11
- const fgOpen = 30, fgClose = 39;
12
- const bgOpen = 40, bgClose = 49;
13
-
14
- function makeChannel(colorDepth: ColorDepth, open: number, close: number): Channel {
15
- const sgrBase = makeSGR(colorDepth > 1);
16
- const style = makeStyle(colorDepth > 1);
17
-
18
- const sgr = (code: number | number[]): SGR => sgrBase(code, close);
19
-
20
- const bright: (index: number) => SGR =
21
- colorDepth >= 4 ? index => sgr(open + 60 + index) :
22
- colorDepth === 3 ?
23
- open === fgOpen ?
24
- index => style.bold.combine(sgr(open + index)) :
25
- index => sgr(open + index) :
26
- () => sgr(0);
27
-
28
- const x16ToX8: (code: number) => SGR =
29
- open === fgOpen ?
30
- code => code >= 60 ? style.bold.combine(sgr(open + code - 60)) : sgr(open + code) :
31
- code => code >= 60 ? sgr(open + code - 60) : sgr(open + code);
32
-
33
- return {
34
- black : sgr(open + 0),
35
- red : sgr(open + 1),
36
- green : sgr(open + 2),
37
- yellow : sgr(open + 3),
38
- blue : sgr(open + 4),
39
- magenta : sgr(open + 5),
40
- cyan : sgr(open + 6),
41
- white : sgr(open + 7),
42
-
43
- brightBlack : bright(0),
44
- brightRed : bright(1),
45
- brightGreen : bright(2),
46
- brightYellow : bright(3),
47
- brightBlue : bright(4),
48
- brightMagenta : bright(5),
49
- brightCyan : bright(6),
50
- brightWhite : bright(7),
51
-
52
- rgb:
53
- colorDepth >= 24 ? (r, g, b) => sgr([open + 8, 2, ...clip(r, g, b)]) :
54
- colorDepth >= 8 ? (r, g, b) => sgr([open + 8, 5, rgbToX256(...clip(r, g, b))]) :
55
- colorDepth >= 4 ? (r, g, b) => sgr(open + rgbToX16(...clip(r, g, b))) :
56
- colorDepth === 3 ?
57
- open === fgOpen ?
58
- (r, g, b) => x16ToX8(rgbToX16(...clip(r, g, b))) :
59
- (r, g, b) => sgr(open + rgbToX8(...clip(r, g, b))) :
60
- () => sgr(0),
61
-
62
- x256:
63
- colorDepth >= 8 ?
64
- (...args: [number] | [number, number, number]): SGR => sgr(
65
- [open + 8, 5, args.length === 3 ? rgbToX256(...clip(...args)) : clip(args[0])],
66
- ) :
67
- colorDepth >= 4 ?
68
- (...args: [number] | [number, number, number]): SGR => sgr(
69
- open +
70
- rgbToX16(...(args.length === 3 ? clip(...args) : x256ToRgb(clip(args[0])))),
71
- ) :
72
- colorDepth === 3 ?
73
- open === fgOpen ?
74
- (...args: [number] | [number, number, number]): SGR => x16ToX8(
75
- args.length === 3
76
- ? rgbToX16(...clip(...args))
77
- : rgbToX16(...x256ToRgb(clip(args[0]))),
78
- ) :
79
- (...args: [number] | [number, number, number]): SGR => sgr(
80
- open +
81
- rgbToX8(...args.length === 3 ? clip(...args) : x256ToRgb(clip(args[0]))),
82
- ) :
83
- (): SGR => sgr(0),
84
-
85
- default: colorDepth > 1 ? `\x1b[${close}m` : "",
86
- };
87
- }
88
-
89
- const cache: Partial<Record<ColorDepth, Color>> = {};
90
-
91
- export const makeColor = (colorDepth: ColorDepth = 4): Color =>
92
- cache[colorDepth] ?? (cache[colorDepth] = {
93
- fg : makeChannel(colorDepth, fgOpen, fgClose),
94
- bg : makeChannel(colorDepth, bgOpen, bgClose),
95
- });
1
+ "use strict";
2
+
3
+ import {lazy} from "../lazy";
4
+ import {makeChannel} from "./channel";
5
+
6
+ import type {ColorDepth} from "../../features";
7
+ import type {ChainKey, Color, FormatBuilder, Style} from "../types";
8
+
9
+ const channels = ["fg", "bg"] as const;
10
+
11
+ export function makeColor(
12
+ keys : ReadonlySet<ChainKey>,
13
+ makeFormatter : FormatBuilder,
14
+ style : Style,
15
+ colorDepth : ColorDepth,
16
+ ): Color<any> {
17
+ const rtn = {} as Color<any>;
18
+
19
+ for (const channel of channels) {
20
+ if (keys.has(channel)) {
21
+ lazy.add(rtn, channel, () => {
22
+ const newKeys = new Set(keys);
23
+ newKeys.delete(channel);
24
+ return makeChannel(newKeys, makeFormatter, style, colorDepth, channel);
25
+ });
26
+ }
27
+ }
28
+
29
+ return rtn;
30
+ }
@@ -0,0 +1,44 @@
1
+ import {combineCodes} from "../combine";
2
+
3
+ import type {ColorDepth} from "../../features";
4
+ import type {ChainKey, Code, ColorKey, Format, FormatBase, FormatBuilder, Style} from "../types";
5
+
6
+ const baseCodes = {
7
+ fg: {open: 30, close: 39},
8
+ bg: {open: 40, close: 49},
9
+ };
10
+
11
+ export function buildContext(
12
+ keys : ReadonlySet<ChainKey>,
13
+ makeFormatter : FormatBuilder,
14
+ style : Style,
15
+ colorDepth : ColorDepth,
16
+ channel : ColorKey
17
+ ) {
18
+ const {open, close} = baseCodes[channel];
19
+
20
+ const build = (open: Code): Format => makeFormatter(keys, open, close);
21
+
22
+ const combine = (base: FormatBase, style: FormatBase): Format => makeFormatter(
23
+ keys,
24
+ combineCodes(base.codes.open, style.codes.open),
25
+ combineCodes(base.codes.close, style.codes.close),
26
+ );
27
+
28
+ const bright: (index: number) => Format =
29
+ colorDepth >= 4 ? index => build(open + 60 + index) :
30
+ colorDepth === 3 ?
31
+ channel === "fg" ?
32
+ index => combine(style.bold, build(open + index)) :
33
+ index => build(open + index) :
34
+ () => build(0);
35
+
36
+ const x16ToX8: (code: number) => Format =
37
+ channel === "fg" ?
38
+ code => code >= 60 ? combine(style.bold, build(open + code - 60)) : build(open + code) :
39
+ code => code >= 60 ? build(open + code - 60) : build(open + code);
40
+
41
+ return {colorDepth, channel, open, close, build, bright, x16ToX8};
42
+ }
43
+
44
+ export type Context = ReturnType<typeof buildContext>;
@@ -1 +1 @@
1
- export * from "./color";
1
+ export * from "./color";
@@ -0,0 +1,48 @@
1
+ import {clip, rgbToX256, rgbToX16, rgbToX8, x256ToRgb} from "./utils";
2
+
3
+ import type {Channel, Format} from "../types";
4
+ import type {Context} from "./context";
5
+
6
+ export function makeRGB(ctx: Context): Channel["rgb"] {
7
+ const {colorDepth, channel, build, open, x16ToX8} = ctx;
8
+
9
+ return (
10
+ colorDepth >= 24 ? (r, g, b) => build([open + 8, 2, ...clip(r, g, b)]) :
11
+ colorDepth >= 8 ? (r, g, b) => build([open + 8, 5, rgbToX256(...clip(r, g, b))]) :
12
+ colorDepth >= 4 ? (r, g, b) => build(open + rgbToX16(...clip(r, g, b))) :
13
+ colorDepth === 3 ?
14
+ channel === "fg" ?
15
+ (r, g, b) => x16ToX8(rgbToX16(...clip(r, g, b))) :
16
+ (r, g, b) => build(open + rgbToX8(...clip(r, g, b))) :
17
+ () => build(0)
18
+ );
19
+ }
20
+
21
+ export function makeX256(ctx: Context): (...args: [number] | [number, number, number]) => Format {
22
+ const {colorDepth, channel, build, open, x16ToX8} = ctx;
23
+
24
+ return (
25
+ colorDepth >= 8 ?
26
+ (...args) => build(
27
+ [open + 8, 5, args.length === 3 ? rgbToX256(...clip(...args)) : clip(args[0])],
28
+ ) :
29
+ colorDepth >= 4 ?
30
+ (...args) => build(
31
+ open +
32
+ rgbToX16(...(args.length === 3 ? clip(...args) : x256ToRgb(clip(args[0])))),
33
+ ) :
34
+ colorDepth === 3 ?
35
+ channel === "fg" ?
36
+ (...args) => x16ToX8(
37
+ args.length === 3 ?
38
+ rgbToX16(...clip(...args)) :
39
+ rgbToX16(...x256ToRgb(clip(args[0]))),
40
+ ) :
41
+ (...args) => build(
42
+ open + rgbToX8(
43
+ ...args.length === 3 ? clip(...args) : x256ToRgb(clip(args[0])),
44
+ ),
45
+ ) :
46
+ () => build(0)
47
+ );
48
+ }
@@ -1,7 +1,10 @@
1
1
  export function rgbToX256(r: number, g: number, b: number): number {
2
- if (r === g && g === b)
2
+ if (r === g && g === b) {
3
+ // grayscale
3
4
  return 232 + Math.floor(r * 24 / 256);
5
+ }
4
6
 
7
+ // 6x6x6 color cube
5
8
  return (
6
9
  16 +
7
10
  Math.floor(r * 6 / 256) * 36 +
@@ -12,6 +15,7 @@ export function rgbToX256(r: number, g: number, b: number): number {
12
15
 
13
16
  export function x256ToRgb(code: number): [number, number, number] {
14
17
  if (code >= 232) {
18
+ // grayscale
15
19
  const gray = Math.round((code - 232) * 255 / 24);
16
20
  return [gray, gray, gray];
17
21
  }
@@ -24,16 +28,21 @@ export function x256ToRgb(code: number): [number, number, number] {
24
28
  }
25
29
 
26
30
  export function rgbToX16(r: number, g: number, b: number): number {
31
+ // First calculate it as a 4x4x4 cube.
32
+
27
33
  r = Math.min(2, Math.floor(r / 85));
28
34
  g = Math.min(2, Math.floor(g / 85));
29
35
  b = Math.min(2, Math.floor(b / 85));
30
36
 
37
+ // Which channels are "on" determines the hue (ANSI color index 0–7).
31
38
  const index = (r ? 1 : 0) | (g ? 2 : 0) | (b ? 4 : 0);
32
39
 
40
+ // The bright bit is derived from the max channel level across all three.
33
41
  const twos = (r === 2 ? 1 : 0) + (g === 2 ? 1 : 0) + (b === 2 ? 1 : 0);
34
42
  const ones = (r === 1 ? 1 : 0) + (g === 1 ? 1 : 0) + (b === 1 ? 1 : 0);
35
43
  const bright = twos > 0 && twos >= ones;
36
44
 
45
+ // Return value uses the SGR offset scheme: 0–7 for normal, 60–67 for bright.
37
46
  return index + (bright ? 60 : 0);
38
47
  }
39
48
 
@@ -48,9 +57,9 @@ export function rgbToX8(r: number, g: number, b: number): number {
48
57
  export function clip(x: number): number;
49
58
  export function clip(r: number, g: number, b: number): [number, number, number];
50
59
  export function clip(...args: [number] | [number, number, number]): number | [number, number, number] {
51
- return args.length === 1
52
- ? Math.max(0, Math.min(255, Math.floor(args[0])))
53
- : [
60
+ return args.length === 1 ?
61
+ Math.max(0, Math.min(255, Math.floor(args[0]))) :
62
+ [
54
63
  Math.max(0, Math.min(255, Math.floor(args[0]))),
55
64
  Math.max(0, Math.min(255, Math.floor(args[1]))),
56
65
  Math.max(0, Math.min(255, Math.floor(args[2]))),
@@ -0,0 +1,13 @@
1
+ import type {Code} from "./types";
2
+
3
+ export function combineCodes(base: Code, code: Code): Code {
4
+ const set = new Set(Array.isArray(base) ? [...base] : [base]);
5
+
6
+ if (Array.isArray(code)) {
7
+ for (const c of code)
8
+ set.add(c);
9
+ } else
10
+ set.add(code);
11
+
12
+ return [...set];
13
+ }
@@ -0,0 +1,47 @@
1
+ import {define} from "@peteanderson/props";
2
+ import {lazy} from "./lazy";
3
+
4
+ import type {ChainBuilder, ChainKey, Code, Format, FormatBase, FormatBuilder} from "./types";
5
+
6
+ const codeSequence = (code: Code): string | number => Array.isArray(code) ? code.join(';') : code;
7
+
8
+ const chain = <Keys extends ChainKey>(
9
+ makeChain : ChainBuilder,
10
+ keys : ReadonlySet<Keys>,
11
+ baseFormat : FormatBase,
12
+ ): Format<Keys> => !keys.size
13
+ ? baseFormat as Format<Keys>
14
+ : lazy.add(baseFormat, "and", () => makeChain(keys, baseFormat));
15
+
16
+ export function makeFormatBuilder(makeChain: ChainBuilder, enabled = true): FormatBuilder {
17
+ if (enabled) {
18
+ return (keys, open, close, reset) => {
19
+ const openCode = codeSequence(open);
20
+ const closeCode = codeSequence(close);
21
+ const openSequence = `\x1b[${openCode}m`;
22
+ const closeSequence = `\x1b[${closeCode}m`;
23
+ const reopenCode = reset ? `${closeCode};${openCode}` : openCode;
24
+
25
+ const rxClose = new RegExp(
26
+ `(?<start>\\x1b\\[(?:\\d+;)*)${closeCode}(?<end>(?:;\\d+)*m)`,
27
+ "g"
28
+ );
29
+ const replace = `$<start>${reopenCode}$<end>`;
30
+
31
+ return chain(makeChain, keys, define(
32
+ (s: string) => s ? openSequence + s.replace(rxClose, replace) + closeSequence : s,
33
+ {
34
+ open : {value: openSequence, enumerable: true},
35
+ close : {value: closeSequence, enumerable: true},
36
+ codes : {value: {open, close}, enumerable: true},
37
+ },
38
+ ));
39
+ };
40
+ }
41
+
42
+ return keys => chain(makeChain, keys, define((s: string) => s, {
43
+ open : {value: "", enumerable: true},
44
+ close : {value: "", enumerable: true},
45
+ codes : {value: {open: [], close: []}, enumerable: true},
46
+ }));
47
+ }
package/src/sgr/index.ts CHANGED
@@ -1,3 +1,3 @@
1
- export * from "./reset";
2
- export * from "./style";
3
- export * from "./color";
1
+ export * from "./sgr";
2
+
3
+ export {SGR} from "./types";
@@ -0,0 +1,21 @@
1
+ import {define} from "@peteanderson/props";
2
+
3
+ import type {GetterProperty} from "@peteanderson/props";
4
+
5
+ export function lazy<K extends keyof any, V>(key: K, get: () => V): GetterProperty<V> {
6
+ return {
7
+ get() {
8
+ const value = get();
9
+ define(this, key, {value, enumerable: true});
10
+ return value;
11
+ },
12
+ enumerable : true,
13
+ configurable : true,
14
+ };
15
+ }
16
+
17
+ lazy.add = <T extends object, K extends keyof any, V>(
18
+ obj : T,
19
+ key : K,
20
+ get : () => V,
21
+ ): T & {[k in K]: V} => define(obj, key, lazy(key, get));
package/src/sgr/sgr.ts CHANGED
@@ -1,68 +1,72 @@
1
- export type Code = number | number[];
1
+ import {TypedObject} from "../typed-object";
2
+ import {makeColor} from "./color";
3
+ import {combineCodes} from "./combine";
4
+ import {makeFormatBuilder} from "./format-builder";
5
+ import {makeReset} from "./reset";
6
+ import {makeStyle} from "./style";
2
7
 
3
- export interface SGR {
4
- (text: string): string;
5
- open : string;
6
- close : string;
7
- codes : {open: Code; close: Code};
8
- combine : (...styles: SGR[]) => SGR;
9
- }
8
+ import type {ColorDepth, Features} from "../features";
9
+ import type {Chain, ChainKey, FormatBase, FormatBuilder, SGR} from "./types";
10
10
 
11
- function combine(code: Code, codes: Code[]): Code {
12
- if (!codes.length)
13
- return code;
11
+ const keyMap: Record<ChainKey, undefined> = {
12
+ fg : undefined,
13
+ bg : undefined,
14
+ bold : undefined,
15
+ dim : undefined,
16
+ italic : undefined,
17
+ underline : undefined,
18
+ inverse : undefined,
19
+ hidden : undefined,
20
+ strikethrough : undefined,
21
+ doubleUnderline : undefined,
22
+ frame : undefined,
23
+ encircle : undefined,
24
+ overline : undefined,
25
+ };
14
26
 
15
- const rtn = Array.isArray(code) ? [...code] : [code];
27
+ const allKeys: ReadonlySet<ChainKey> = new Set(TypedObject.keys(keyMap));
16
28
 
17
- for (const c of codes) {
18
- if (Array.isArray(c))
19
- rtn.push(...c);
20
- else
21
- rtn.push(c);
22
- }
23
-
24
- return rtn;
25
- }
29
+ const cache: Partial<Record<`${ColorDepth}.${boolean}`, SGR>> = {};
26
30
 
27
- const codeSequence = (code: Code): string | number => Array.isArray(code) ? code.join(";") : code;
31
+ export function makeSGR(features: Features): SGR {
32
+ const cacheKey = `${features.colorDepth}.${features.style}` as const;
33
+ if (cache[cacheKey])
34
+ return cache[cacheKey];
28
35
 
29
- function sgr(open: Code, close: Code, reset = false): SGR {
30
- const openCode = codeSequence(open);
31
- const closeCode = codeSequence(close);
32
- const openSequence = `\x1b[${openCode}m`;
33
- const closeSequence = `\x1b[${closeCode}m`;
34
- const reopenCode = reset ? `${closeCode};${openCode}` : openCode;
36
+ const colorFormat = makeFormatBuilder(makeChain, features.colorDepth > 1);
37
+ const styleFormat = makeFormatBuilder(makeChain, features.style);
38
+ const style = makeStyle(allKeys, styleFormat, features.style);
35
39
 
36
- const rxClose = new RegExp(
37
- `(?<start>\\x1b\\[(?:\\d+;)*)${closeCode}(?<end>(?:;\\d+)*m)`,
38
- "g"
39
- );
40
- const replace = `$<start>${reopenCode}$<end>`;
40
+ function makeChain<Keys extends ChainKey>(
41
+ keys : ReadonlySet<Keys>,
42
+ baseFormat : FormatBase,
43
+ ): Chain<Keys> {
44
+ const chainedColorFormat: FormatBuilder = (keys, open, close, reset) => colorFormat(
45
+ keys,
46
+ combineCodes(baseFormat.codes.open, open),
47
+ combineCodes(baseFormat.codes.close, close),
48
+ reset,
49
+ );
50
+ const chainedStyleFormat: FormatBuilder = (keys, open, close, reset) => styleFormat(
51
+ keys,
52
+ combineCodes(baseFormat.codes.open, open),
53
+ combineCodes(baseFormat.codes.close, close),
54
+ reset,
55
+ );
41
56
 
42
- const func = (s: string) => s ? openSequence + s.replace(rxClose, replace) + closeSequence : s;
43
-
44
- return Object.assign(func, {
45
- codes : {open, close},
46
- open : openSequence,
47
- close : closeSequence,
48
- combine : (...styles: SGR[]) => sgr(
49
- combine(open, styles.map(s => s.codes.open)),
50
- combine(close, styles.map(s => s.codes.close)),
51
- ),
52
- });
53
- }
57
+ return /** @type {Chain<any>} */({
58
+ ...makeColor(keys, chainedColorFormat, style, features.colorDepth),
59
+ ...makeStyle(keys, chainedStyleFormat, features.style),
60
+ });
61
+ }
54
62
 
55
- let disabledSGR = null as unknown as SGR;
56
- disabledSGR = Object.assign(
57
- (s: string) => s,
58
- {
59
- codes : {open: 0, close: 0},
60
- open : "",
61
- close : "",
62
- combine : (): SGR => disabledSGR,
63
- },
64
- );
63
+ const rtn: SGR = {
64
+ ...makeColor(allKeys, colorFormat, style, features.colorDepth),
65
+ style,
66
+ reset: makeReset(features.colorDepth > 1 || features.style),
67
+ };
65
68
 
66
- const disabled = (): SGR => disabledSGR;
69
+ cache[cacheKey] = rtn;
67
70
 
68
- export const makeSGR = (enabled = true): typeof sgr => enabled ? sgr : disabled;
71
+ return rtn;
72
+ }
package/src/sgr/style.ts CHANGED
@@ -1,21 +1,38 @@
1
- import {makeSGR} from "./sgr";
1
+ import {TypedObject} from "../typed-object";
2
+ import {lazy} from "./lazy";
2
3
 
3
- export function makeStyle(enabled = true) {
4
- const sgr = makeSGR(enabled);
4
+ import type {ChainKey, FormatBuilder, Style} from "./types";
5
5
 
6
- return {
7
- bold : sgr(1, 22, true),
8
- dim : sgr(2, 22, true),
9
- italic : sgr(3, 23),
10
- underline : sgr(4, 24),
11
- inverse : sgr(7, 27),
12
- hidden : sgr(8, 28),
13
- strikethrough : sgr(9, 29),
14
- doubleUnderline : sgr(21, 24),
15
- frame : sgr(51, 54),
16
- encircle : sgr(52, 54),
17
- overline : sgr(53, 55),
18
- };
19
- }
6
+ const propParams = TypedObject.entries<Record<keyof Style, SkipFirst<Parameters<FormatBuilder>>>>({
7
+ bold : [ 1, 22, true],
8
+ dim : [ 2, 22, true],
9
+ italic : [ 3, 23],
10
+ underline : [ 4, 24],
11
+ inverse : [ 7, 27],
12
+ hidden : [ 8, 28],
13
+ strikethrough : [ 9, 29],
14
+ doubleUnderline : [21, 24],
15
+ frame : [51, 54],
16
+ encircle : [52, 54],
17
+ overline : [53, 55],
18
+ });
20
19
 
21
- export type Style = ReturnType<typeof makeStyle>;
20
+ export function makeStyle(
21
+ keys : ReadonlySet<ChainKey>,
22
+ makeFormatter : FormatBuilder,
23
+ enabled : boolean,
24
+ ): Style<any> {
25
+ const rtn = {} as Style<any>;
26
+
27
+ for (const [key, args] of propParams) {
28
+ if (keys.has(key)) {
29
+ lazy.add(rtn, key, () => {
30
+ const newKeys = new Set(keys);
31
+ newKeys.delete(key);
32
+ return enabled ? makeFormatter(newKeys, ...args) : makeFormatter(newKeys, 0, 0);
33
+ });
34
+ }
35
+ }
36
+
37
+ return rtn;
38
+ }