@peteanderson/ansi 1.0.1 → 2.0.1

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 (107) hide show
  1. package/README.md +23 -3
  2. package/dist/ansi.d.ts +79 -41
  3. package/dist/ansi.d.ts.map +1 -1
  4. package/dist/ansi.js +9 -6
  5. package/dist/ansi.js.map +1 -1
  6. package/dist/features/features.d.ts +1 -3
  7. package/dist/features/features.d.ts.map +1 -1
  8. package/dist/features/features.js +1 -1
  9. package/dist/features/features.js.map +1 -1
  10. package/dist/features/index.d.ts +1 -0
  11. package/dist/features/index.d.ts.map +1 -1
  12. package/dist/features/types.d.ts +2 -0
  13. package/dist/features/types.d.ts.map +1 -1
  14. package/dist/pad.d.ts +2 -0
  15. package/dist/pad.d.ts.map +1 -1
  16. package/dist/sgr/color/channel.d.ts +4 -0
  17. package/dist/sgr/color/channel.d.ts.map +1 -0
  18. package/dist/sgr/color/channel.js +33 -0
  19. package/dist/sgr/color/channel.js.map +1 -0
  20. package/dist/sgr/color/color.d.ts +3 -4
  21. package/dist/sgr/color/color.d.ts.map +1 -1
  22. package/dist/sgr/color/color.js +16 -65
  23. package/dist/sgr/color/color.js.map +1 -1
  24. package/dist/sgr/color/context.d.ts +13 -0
  25. package/dist/sgr/color/context.d.ts.map +1 -0
  26. package/dist/sgr/color/context.js +24 -0
  27. package/dist/sgr/color/context.js.map +1 -0
  28. package/dist/sgr/color/rgb.d.ts +5 -0
  29. package/dist/sgr/color/rgb.d.ts.map +1 -0
  30. package/dist/sgr/color/rgb.js +32 -0
  31. package/dist/sgr/color/rgb.js.map +1 -0
  32. package/dist/sgr/color/utils.d.ts.map +1 -1
  33. package/dist/sgr/color/utils.js.map +1 -1
  34. package/dist/sgr/combine.d.ts +3 -0
  35. package/dist/sgr/combine.d.ts.map +1 -0
  36. package/dist/sgr/combine.js +14 -0
  37. package/dist/sgr/combine.js.map +1 -0
  38. package/dist/sgr/format-builder.d.ts +3 -0
  39. package/dist/sgr/format-builder.d.ts.map +1 -0
  40. package/dist/sgr/format-builder.js +33 -0
  41. package/dist/sgr/format-builder.js.map +1 -0
  42. package/dist/sgr/index.d.ts +2 -3
  43. package/dist/sgr/index.d.ts.map +1 -1
  44. package/dist/sgr/index.js +1 -3
  45. package/dist/sgr/index.js.map +1 -1
  46. package/dist/sgr/lazy.d.ts +6 -0
  47. package/dist/sgr/lazy.d.ts.map +1 -0
  48. package/dist/sgr/lazy.js +17 -0
  49. package/dist/sgr/lazy.js.map +1 -0
  50. package/dist/sgr/sgr.d.ts +3 -14
  51. package/dist/sgr/sgr.d.ts.map +1 -1
  52. package/dist/sgr/sgr.js +45 -39
  53. package/dist/sgr/sgr.js.map +1 -1
  54. package/dist/sgr/style.d.ts +2 -14
  55. package/dist/sgr/style.d.ts.map +1 -1
  56. package/dist/sgr/style.js +27 -16
  57. package/dist/sgr/style.js.map +1 -1
  58. package/dist/sgr/types.d.ts +59 -0
  59. package/dist/sgr/types.d.ts.map +1 -0
  60. package/dist/sgr/{color/types.js.map → types.js.map} +1 -1
  61. package/dist/simplify/simplify.d.ts +1 -0
  62. package/dist/simplify/simplify.d.ts.map +1 -1
  63. package/dist/slice.d.ts +1 -0
  64. package/dist/slice.d.ts.map +1 -1
  65. package/dist/strip.d.ts +3 -0
  66. package/dist/strip.d.ts.map +1 -1
  67. package/dist/typed-object.d.ts +16 -0
  68. package/dist/typed-object.d.ts.map +1 -0
  69. package/dist/typed-object.js +36 -0
  70. package/dist/typed-object.js.map +1 -0
  71. package/dist/types.d.ts +25 -0
  72. package/dist/types.d.ts.map +1 -0
  73. package/dist/types.js +3 -0
  74. package/dist/types.js.map +1 -0
  75. package/package.json +3 -2
  76. package/src/ansi.ts +11 -36
  77. package/src/features/features.ts +2 -9
  78. package/src/features/index.ts +3 -1
  79. package/src/features/types.ts +10 -1
  80. package/src/pad.ts +4 -1
  81. package/src/sgr/color/channel.ts +44 -0
  82. package/src/sgr/color/color.ts +30 -95
  83. package/src/sgr/color/context.ts +44 -0
  84. package/src/sgr/color/rgb.ts +48 -0
  85. package/src/sgr/color/utils.ts +2 -3
  86. package/src/sgr/combine.ts +13 -0
  87. package/src/sgr/format-builder.ts +47 -0
  88. package/src/sgr/index.ts +3 -3
  89. package/src/sgr/lazy.ts +21 -0
  90. package/src/sgr/sgr.ts +61 -54
  91. package/src/sgr/style.ts +35 -18
  92. package/src/sgr/types.ts +91 -0
  93. package/src/simplify/simplify.ts +3 -1
  94. package/src/slice.ts +3 -1
  95. package/src/strip.ts +5 -1
  96. package/src/typed-object.ts +85 -0
  97. package/src/types.ts +28 -0
  98. package/src/utility-types.d.ts +18 -0
  99. package/dist/sgr/color/types.d.ts +0 -30
  100. package/dist/sgr/color/types.d.ts.map +0 -1
  101. package/dist/sgr/utils.d.ts +0 -3
  102. package/dist/sgr/utils.d.ts.map +0 -1
  103. package/dist/sgr/utils.js +0 -16
  104. package/dist/sgr/utils.js.map +0 -1
  105. package/src/sgr/color/types.ts +0 -32
  106. package/src/sgr/utils.ts +0 -17
  107. /package/dist/sgr/{color/types.js → types.js} +0 -0
package/src/ansi.ts CHANGED
@@ -3,54 +3,25 @@ import {makeErase} from "./erase";
3
3
  import {getFeatures} from "./features";
4
4
  import {padEnd, padStart} from "./pad";
5
5
  import {makeScroll} from "./scroll";
6
- import {makeColor, makeReset, makeStyle} from "./sgr";
6
+ import {makeSGR} from "./sgr";
7
7
  import {simplify} from "./simplify";
8
8
  import {slice} from "./slice";
9
9
  import {sanitize, strip, visibleLength} from "./strip";
10
10
  import {makeTerminal} from "./terminal";
11
11
 
12
- import type {Args} from "./features/features";
13
- import type {Caret} from "./caret";
14
- import type {Erase} from "./erase";
15
- import type {Features} from "./features/types";
16
- import type {Scroll} from "./scroll";
17
- import type {Color, Style} from "./sgr";
18
- import type {Terminal} from "./terminal";
12
+ import type {Args} from "./features";
13
+ import type {Ansi} from "./types";
19
14
 
20
- export type Ansi = Color & {
21
- caret : Caret;
22
- erase : Erase;
23
- scroll : Scroll;
24
-
25
- reset : string;
26
- style : Style;
27
-
28
- terminal : Terminal;
29
-
30
- padEnd : typeof padEnd;
31
- padStart : typeof padStart;
32
-
33
- strip : typeof strip;
34
- visibleLength : typeof visibleLength;
35
- sanitize : typeof sanitize;
36
- slice : typeof slice;
37
- simplify : typeof simplify;
38
-
39
- features : Features;
40
- };
41
-
42
- export function makeAnsi(...args: Args): Ansi {
15
+ function makeAnsi(...args: Args): Ansi {
43
16
  const features = getFeatures(...args);
44
17
 
45
18
  return {
19
+ ...makeSGR(features),
20
+
46
21
  caret : makeCaret(features.caret),
47
22
  erase : makeErase(features.erase),
48
23
  scroll : makeScroll(features.scroll),
49
24
 
50
- reset : makeReset(features.colorDepth > 1),
51
- style : makeStyle(features.style),
52
- ...makeColor(features.colorDepth),
53
-
54
25
  terminal: makeTerminal(features.terminal),
55
26
 
56
27
  padEnd,
@@ -66,6 +37,10 @@ export function makeAnsi(...args: Args): Ansi {
66
37
  };
67
38
  }
68
39
 
69
- const ansi = Object.assign(makeAnsi, makeAnsi());
40
+ export const ansi = Object.assign(makeAnsi, makeAnsi());
41
+
42
+ export const {fg, bg, style, reset, caret, erase, scroll, terminal} = ansi;
43
+
44
+ export {padEnd, padStart, strip, visibleLength}
70
45
 
71
46
  export default ansi;
@@ -3,14 +3,7 @@ import {WriteStream} from "node:tty";
3
3
  import {boolean, build, number, pipe, tty} from "./build";
4
4
  import {getColorDepth} from "./detect";
5
5
 
6
- import type {ColorDepth, Features} from "./types";
7
-
8
- export type Args =
9
- | [enabled : boolean, stream?: NodeJS.WriteStream]
10
- | [colorDepth : ColorDepth, stream?: NodeJS.WriteStream]
11
- | [features : Partial<Features>, stream?: NodeJS.WriteStream]
12
- | [socket : Socket]
13
- | [stream? : NodeJS.WriteStream];
6
+ import type {Args, Features} from "./types";
14
7
 
15
8
  export function getFeatures(...args: Args): Features {
16
9
  let [features, stream] = args;
@@ -35,7 +28,7 @@ export function getFeatures(...args: Args): Features {
35
28
  stream ??= process.stdout;
36
29
 
37
30
  if (colorDepth)
38
- build(stream.isTTY, colorDepth, style, caret, erase, scroll, terminal);
31
+ return build(stream.isTTY, colorDepth, style, caret, erase, scroll, terminal);
39
32
 
40
33
  if (stream.isTTY)
41
34
  return build(true, getColorDepth(stream), style, caret, erase, scroll, terminal);
@@ -1 +1,3 @@
1
- export * from "./features";
1
+ export * from "./features";
2
+
3
+ export type * from "./types";
@@ -1,3 +1,5 @@
1
+ import type {Socket} from "node:net";
2
+
1
3
  export type ColorDepth = 1 | 3 | 4 | 8 | 24;
2
4
 
3
5
  export interface Features {
@@ -7,4 +9,11 @@ export interface Features {
7
9
  erase : boolean;
8
10
  scroll : boolean;
9
11
  terminal : boolean;
10
- }
12
+ }
13
+
14
+ export type Args =
15
+ | [enabled : boolean, stream?: NodeJS.WriteStream]
16
+ | [colorDepth : ColorDepth, stream?: NodeJS.WriteStream]
17
+ | [features : Partial<Features>, stream?: NodeJS.WriteStream]
18
+ | [socket : Socket]
19
+ | [stream? : NodeJS.WriteStream];
package/src/pad.ts CHANGED
@@ -12,4 +12,7 @@ export const padEnd = (text: string, targetLength: number, fillString = " "): st
12
12
  * without counting ANSI escape codes.
13
13
  */
14
14
  export const padStart = (text: string, targetLength: number, fillString = " "): string =>
15
- fillString.repeat(Math.max(0, targetLength - visibleLength(text)) / fillString.length) + text;
15
+ fillString.repeat(Math.max(0, targetLength - visibleLength(text)) / fillString.length) + text;
16
+
17
+ export type PadEnd = typeof padEnd;
18
+ export type PadStart = typeof padStart;
@@ -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>;
@@ -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
+ }
@@ -29,6 +29,7 @@ export function x256ToRgb(code: number): [number, number, number] {
29
29
 
30
30
  export function rgbToX16(r: number, g: number, b: number): number {
31
31
  // First calculate it as a 4x4x4 cube.
32
+
32
33
  r = Math.min(2, Math.floor(r / 85));
33
34
  g = Math.min(2, Math.floor(g / 85));
34
35
  b = Math.min(2, Math.floor(b / 85));
@@ -55,9 +56,7 @@ export function rgbToX8(r: number, g: number, b: number): number {
55
56
 
56
57
  export function clip(x: number): number;
57
58
  export function clip(r: number, g: number, b: number): [number, number, number];
58
- export function clip(
59
- ...args: [number] | [number, number, number]
60
- ): number | [number, number, number] {
59
+ export function clip(...args: [number] | [number, number, number]): number | [number, number, number] {
61
60
  return args.length === 1 ?
62
61
  Math.max(0, Math.min(255, Math.floor(args[0]))) :
63
62
  [
@@ -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,65 +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 set = new Set(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
- for (const code of c)
20
- set.add(code);
21
- } else
22
- set.add(c);
23
- }
24
-
25
- return Array.from(set);
26
- }
29
+ const cache: Partial<Record<`${ColorDepth}.${boolean}`, SGR>> = {};
27
30
 
28
- 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];
29
35
 
30
- function sgr(open: Code, close: Code, reset = false): SGR {
31
- const openCode = codeSequence(open);
32
- const closeCode = codeSequence(close);
33
- const openSequence = `\x1b[${openCode}m`;
34
- const closeSequence = `\x1b[${closeCode}m`;
35
- 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);
36
39
 
37
- const rxClose = new RegExp(
38
- `(?<start>\\x1b\\[(?:\\d+;)*)${closeCode}(?<end>(?:;\\d+)*m)`,
39
- "g"
40
- );
41
- 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
+ );
42
56
 
43
- const func = (s: string) => s ? openSequence + s.replace(rxClose, replace) + closeSequence : s;
44
-
45
- return Object.assign(func, {
46
- codes : {open, close},
47
- open : openSequence,
48
- close : closeSequence,
49
- combine : (...styles: SGR[]) => sgr(
50
- combine(open, styles.map(s => s.codes.open)),
51
- combine(close, styles.map(s => s.codes.close)),
52
- ),
53
- });
54
- }
57
+ return /** @type {Chain<any>} */({
58
+ ...makeColor(keys, chainedColorFormat, style, features.colorDepth),
59
+ ...makeStyle(keys, chainedStyleFormat, features.style),
60
+ });
61
+ }
55
62
 
56
- const disabledSGR: SGR = Object.assign((s: string) => s, {
57
- codes : {open: 0, close: 0},
58
- open : "",
59
- close : "",
60
- combine : (): SGR => disabledSGR,
61
- });
63
+ const rtn: SGR = {
64
+ ...makeColor(allKeys, colorFormat, style, features.colorDepth),
65
+ style,
66
+ reset: makeReset(features.colorDepth > 1 || features.style),
67
+ };
62
68
 
63
- const disabled: typeof sgr = () => disabledSGR;
69
+ cache[cacheKey] = rtn;
64
70
 
65
- export const makeSGR = (enabled = true): typeof sgr => enabled ? sgr : disabled;
71
+ return rtn;
72
+ }