@dheerajsom/pinhub 0.1.2 → 0.2.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.
package/dist/cli.js CHANGED
@@ -1,6 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  import { runCli } from "./run.js";
3
- const result = await runCli(process.argv.slice(2));
3
+ import { playSignalPulse, shouldAnimateSignalPulse, signalPulseLabel, } from "./status.js";
4
+ const argv = process.argv.slice(2);
5
+ const resultPromise = runCli(argv);
6
+ const pulsePromise = shouldAnimateSignalPulse(argv, {
7
+ stdoutIsTTY: Boolean(process.stdout.isTTY),
8
+ stderrIsTTY: Boolean(process.stderr.isTTY),
9
+ stderrColumns: process.stderr.columns,
10
+ env: process.env,
11
+ })
12
+ ? playSignalPulse(process.stderr, {
13
+ ascii: argv.includes("--ascii"),
14
+ label: signalPulseLabel(argv),
15
+ })
16
+ : Promise.resolve();
17
+ // Await both concurrently so an unexpected command failure is observed
18
+ // immediately instead of becoming an unhandled rejection during the pulse.
19
+ const [result] = await Promise.all([resultPromise, pulsePromise]);
4
20
  if (result.stdout)
5
21
  process.stdout.write(result.stdout);
6
22
  if (result.stderr)
package/dist/model.d.ts CHANGED
@@ -9,10 +9,13 @@ export type Warning = {
9
9
  severity: WarningSeverity;
10
10
  text: string;
11
11
  };
12
+ export type SourceType = "Docs" | "Datasheet" | "Manual" | "Schematic" | "Pinout";
12
13
  export type Source = {
13
14
  title: string;
14
15
  url: string;
15
16
  official: boolean;
17
+ /** What the source documents, used to prefer an exact pin map for wiring. */
18
+ type?: SourceType;
16
19
  };
17
20
  export type Pin = {
18
21
  /** Physical pin number as printed or conventionally counted on the board. */
@@ -1,12 +1,14 @@
1
1
  import type { ChalkInstance } from "chalk";
2
2
  import type { Board } from "../model.js";
3
- import type { CharSet } from "./chars.js";
3
+ import { type CharSet } from "./chars.js";
4
+ export { wrapText } from "./text.js";
4
5
  export type RenderOptions = {
5
6
  chalk: ChalkInstance;
6
7
  chars: CharSet;
7
8
  width: number;
8
9
  compact: boolean;
10
+ /** Show every alternate pin function, in addition to always-visible notes. */
11
+ details?: boolean;
9
12
  };
10
- export declare function wrapText(text: string, width: number, indent: string): string[];
11
13
  export declare function renderWarnings(board: Board, opts: RenderOptions): string[];
12
14
  export declare function renderBoard(board: Board, opts: RenderOptions): string;
@@ -1,140 +1,187 @@
1
+ import { asciiChars } from "./chars.js";
2
+ import { normalizeWidth, padEndVisible, padStartVisible, toAscii, visibleWidth, wrapText, } from "./text.js";
1
3
  import { pinStyle, warningStyle } from "./theme.js";
4
+ export { wrapText } from "./text.js";
2
5
  const INDENT = " ";
3
- /** Label plus its primary alternate function; reserved pins get a `*` marker. */
4
- function displayLabel(pin) {
6
+ function renderWidth(opts) {
7
+ return normalizeWidth(opts.width);
8
+ }
9
+ function finish(text, opts) {
10
+ return opts.chars === asciiChars ? toAscii(text) : text;
11
+ }
12
+ /** Label plus its primary alternate function and safety-note markers. */
13
+ function displayLabel(pin, ch) {
5
14
  const fn = pin.functions?.[0];
6
15
  const base = fn && fn !== pin.label ? `${pin.label} / ${fn}` : pin.label;
7
- return pin.category === "reserved" ? `${base} *` : base;
16
+ const reserved = pin.category === "reserved" ? " *" : "";
17
+ const noted = pin.notes?.length ? ` ${ch.note ?? "^"}` : "";
18
+ return `${base}${reserved}${noted}`;
8
19
  }
9
20
  function pinDetails(pin) {
10
21
  return pin.voltage ? `${pin.category}, ${pin.voltage}` : pin.category;
11
22
  }
12
- export function wrapText(text, width, indent) {
13
- const usable = Math.max(20, width - indent.length);
14
- const words = text.split(/\s+/);
15
- const lines = [];
16
- let line = "";
17
- for (const word of words) {
18
- if (line && line.length + 1 + word.length > usable) {
19
- lines.push(indent + line);
20
- line = word;
21
- }
22
- else {
23
- line = line ? `${line} ${word}` : word;
24
- }
25
- }
26
- if (line)
27
- lines.push(indent + line);
28
- return lines;
29
- }
30
23
  function byPosition(header, column, row) {
31
- return header.pins.find((p) => p.position.column === column && p.position.row === row);
24
+ return header.pins.find((pin) => pin.position.column === column && pin.position.row === row);
32
25
  }
33
26
  /** Bordered two-column diagram: label | pin | pin | label. */
34
27
  function renderDualRowTable(header, opts) {
35
28
  const { chalk: c, chars: ch } = opts;
36
29
  const rows = [];
37
- for (let r = 1; r <= header.layout.rows; r++) {
38
- rows.push([byPosition(header, 1, r), byPosition(header, 2, r)]);
30
+ for (let row = 1; row <= header.layout.rows; row += 1) {
31
+ rows.push([byPosition(header, 1, row), byPosition(header, 2, row)]);
39
32
  }
40
- const leftWidth = Math.max(...rows.map(([l]) => (l ? displayLabel(l).length : 0)), 4);
41
- const rightWidth = Math.max(...rows.map(([, r]) => (r ? displayLabel(r).length : 0)), 4);
42
- const numWidth = Math.max(...header.pins.map((p) => String(p.physical).length), 2);
43
- const totalWidth = INDENT.length + leftWidth + rightWidth + 2 * numWidth + 13;
44
- if (totalWidth > opts.width)
33
+ const leftWidth = Math.max(...rows.map(([left]) => (left ? visibleWidth(displayLabel(left, ch)) : 0)), 4);
34
+ const rightWidth = Math.max(...rows.map(([, right]) => (right ? visibleWidth(displayLabel(right, ch)) : 0)), 4);
35
+ const numWidth = Math.max(...header.pins.map((pin) => String(pin.physical).length), 2);
36
+ const horizontal = ch.horizontal;
37
+ const segment = (width) => horizontal.repeat(width + 2);
38
+ const top = `${INDENT}${ch.topLeft}${segment(leftWidth)}${ch.topJoin}${segment(numWidth)}${ch.topJoin}${segment(numWidth)}${ch.topJoin}${segment(rightWidth)}${ch.topRight}`;
39
+ if (visibleWidth(top) > renderWidth(opts))
45
40
  return undefined;
46
- const h = ch.horizontal;
47
- const seg = (n) => h.repeat(n + 2);
48
- const lines = [];
49
- lines.push(`${INDENT}${ch.topLeft}${seg(leftWidth)}${ch.topJoin}${seg(numWidth)}${ch.topJoin}${seg(numWidth)}${ch.topJoin}${seg(rightWidth)}${ch.topRight}`);
41
+ const lines = [top];
50
42
  for (const [left, right] of rows) {
51
- const leftLabel = left ? pinStyle(c, left)(displayLabel(left).padEnd(leftWidth)) : " ".repeat(leftWidth);
52
- const rightLabel = right ? pinStyle(c, right)(displayLabel(right).padEnd(rightWidth)) : " ".repeat(rightWidth);
43
+ const leftLabel = left
44
+ ? pinStyle(c, left)(padEndVisible(displayLabel(left, ch), leftWidth))
45
+ : " ".repeat(leftWidth);
46
+ const rightLabel = right
47
+ ? pinStyle(c, right)(padEndVisible(displayLabel(right, ch), rightWidth))
48
+ : " ".repeat(rightWidth);
53
49
  const leftNum = left ? String(left.physical).padStart(numWidth) : " ".repeat(numWidth);
54
50
  const rightNum = right ? String(right.physical).padStart(numWidth) : " ".repeat(numWidth);
55
51
  lines.push(`${INDENT}${ch.vertical} ${leftLabel} ${ch.vertical} ${leftNum} ${ch.vertical} ${rightNum} ${ch.vertical} ${rightLabel} ${ch.vertical}`);
56
52
  }
57
- lines.push(`${INDENT}${ch.bottomLeft}${seg(leftWidth)}${ch.bottomJoin}${seg(numWidth)}${ch.bottomJoin}${seg(numWidth)}${ch.bottomJoin}${seg(rightWidth)}${ch.bottomRight}`);
53
+ lines.push(`${INDENT}${ch.bottomLeft}${segment(leftWidth)}${ch.bottomJoin}${segment(numWidth)}${ch.bottomJoin}${segment(numWidth)}${ch.bottomJoin}${segment(rightWidth)}${ch.bottomRight}`);
58
54
  return lines;
59
55
  }
60
56
  /** Borderless two-column pairs for --compact / narrower terminals. */
61
57
  function renderDualRowCompact(header, opts) {
62
58
  const { chalk: c, chars: ch } = opts;
63
59
  const rows = [];
64
- for (let r = 1; r <= header.layout.rows; r++) {
65
- rows.push([byPosition(header, 1, r), byPosition(header, 2, r)]);
60
+ for (let row = 1; row <= header.layout.rows; row += 1) {
61
+ rows.push([byPosition(header, 1, row), byPosition(header, 2, row)]);
66
62
  }
67
- const leftWidth = Math.max(...rows.map(([l]) => (l ? displayLabel(l).length : 0)), 4);
68
- const rightWidth = Math.max(...rows.map(([, r]) => (r ? displayLabel(r).length : 0)), 4);
69
- const numWidth = Math.max(...header.pins.map((p) => String(p.physical).length), 2);
70
- const totalWidth = INDENT.length + leftWidth + 2 + numWidth + 3 + numWidth + 2 + rightWidth;
71
- if (totalWidth > opts.width)
63
+ const leftWidth = Math.max(...rows.map(([left]) => (left ? visibleWidth(displayLabel(left, ch)) : 0)), 4);
64
+ const rightWidth = Math.max(...rows.map(([, right]) => (right ? visibleWidth(displayLabel(right, ch)) : 0)), 4);
65
+ const numWidth = Math.max(...header.pins.map((pin) => String(pin.physical).length), 2);
66
+ const widest = `${INDENT}${" ".repeat(leftWidth)} ${" ".repeat(numWidth)} ${ch.pairSeparator} ${" ".repeat(numWidth)} ${" ".repeat(rightWidth)}`;
67
+ if (visibleWidth(widest) > renderWidth(opts))
72
68
  return undefined;
73
69
  return rows.map(([left, right]) => {
74
- const leftLabel = left ? pinStyle(c, left)(displayLabel(left).padStart(leftWidth)) : " ".repeat(leftWidth);
75
- const rightLabel = right ? pinStyle(c, right)(displayLabel(right)) : "";
70
+ const leftLabel = left
71
+ ? pinStyle(c, left)(padStartVisible(displayLabel(left, ch), leftWidth))
72
+ : " ".repeat(leftWidth);
73
+ const rightLabel = right ? pinStyle(c, right)(displayLabel(right, ch)) : "";
76
74
  const leftNum = left ? String(left.physical).padStart(numWidth) : " ".repeat(numWidth);
77
75
  const rightNum = right ? String(right.physical).padEnd(numWidth) : " ".repeat(numWidth);
78
76
  return `${INDENT}${leftLabel} ${leftNum} ${ch.pairSeparator} ${rightNum} ${rightLabel}`.trimEnd();
79
77
  });
80
78
  }
81
- /** One pin per line always fits; last-resort fallback and 1-column layout. */
79
+ /** One pin per line, wrapping label and details independently when necessary. */
82
80
  function renderPinList(header, opts) {
83
- const { chalk: c } = opts;
84
- const pins = [...header.pins].sort((a, b) => a.physical - b.physical);
85
- const numWidth = Math.max(...pins.map((p) => String(p.physical).length), 2);
86
- const labelWidth = Math.max(...pins.map((p) => displayLabel(p).length), 4);
87
- return pins.map((pin) => {
88
- const label = pinStyle(c, pin)(displayLabel(pin).padEnd(labelWidth));
89
- return `${INDENT}${String(pin.physical).padStart(numWidth)} ${label} ${c.dim(pinDetails(pin))}`;
81
+ const { chalk: c, chars: ch } = opts;
82
+ const width = renderWidth(opts);
83
+ const pins = [...header.pins].sort((left, right) => left.physical - right.physical);
84
+ const numWidth = Math.max(...pins.map((pin) => String(pin.physical).length), 2);
85
+ const labelWidth = Math.max(...pins.map((pin) => visibleWidth(displayLabel(pin, ch))), 4);
86
+ const detailWidth = Math.max(...pins.map((pin) => visibleWidth(pinDetails(pin))), 4);
87
+ const alignedWidth = visibleWidth(INDENT) + numWidth + 2 + labelWidth + 2 + detailWidth;
88
+ return pins.flatMap((pin) => {
89
+ const number = String(pin.physical).padStart(numWidth);
90
+ const label = displayLabel(pin, ch);
91
+ const details = pinDetails(pin);
92
+ if (alignedWidth <= width) {
93
+ return [
94
+ `${INDENT}${number} ${pinStyle(c, pin)(padEndVisible(label, labelWidth))} ${c.dim(details)}`,
95
+ ];
96
+ }
97
+ const singleLine = `${INDENT}${number} ${label} ${details}`;
98
+ if (visibleWidth(singleLine) <= width) {
99
+ return [`${INDENT}${number} ${pinStyle(c, pin)(label)} ${c.dim(details)}`];
100
+ }
101
+ const prefix = `${INDENT}${number} `;
102
+ const continuation = " ".repeat(visibleWidth(prefix));
103
+ const labelLines = wrapText(label, width, prefix, continuation).map((line) => pinStyle(c, pin)(line));
104
+ const detailLines = wrapText(details, width, continuation, continuation).map((line) => c.dim(line));
105
+ return [...labelLines, ...detailLines];
90
106
  });
91
107
  }
92
108
  /** Bordered single-column table: # | pin | details. */
93
109
  function renderSingleColumnTable(header, opts) {
94
110
  const { chalk: c, chars: ch } = opts;
95
- const pins = [...header.pins].sort((a, b) => a.physical - b.physical);
96
- const numWidth = Math.max(...pins.map((p) => String(p.physical).length), 2);
97
- const labelWidth = Math.max(...pins.map((p) => displayLabel(p).length), 4);
98
- const detailWidth = Math.max(...pins.map((p) => pinDetails(p).length), 4);
99
- const totalWidth = INDENT.length + numWidth + labelWidth + detailWidth + 10;
100
- if (totalWidth > opts.width)
111
+ const pins = [...header.pins].sort((left, right) => left.physical - right.physical);
112
+ const numWidth = Math.max(...pins.map((pin) => String(pin.physical).length), 2);
113
+ const labelWidth = Math.max(...pins.map((pin) => visibleWidth(displayLabel(pin, ch))), 4);
114
+ const detailWidth = Math.max(...pins.map((pin) => visibleWidth(pinDetails(pin))), 4);
115
+ const horizontal = ch.horizontal;
116
+ const segment = (width) => horizontal.repeat(width + 2);
117
+ const top = `${INDENT}${ch.topLeft}${segment(numWidth)}${ch.topJoin}${segment(labelWidth)}${ch.topJoin}${segment(detailWidth)}${ch.topRight}`;
118
+ if (visibleWidth(top) > renderWidth(opts))
101
119
  return undefined;
102
- const h = ch.horizontal;
103
- const seg = (n) => h.repeat(n + 2);
104
- const lines = [];
105
- lines.push(`${INDENT}${ch.topLeft}${seg(numWidth)}${ch.topJoin}${seg(labelWidth)}${ch.topJoin}${seg(detailWidth)}${ch.topRight}`);
120
+ const lines = [top];
106
121
  for (const pin of pins) {
107
- const label = pinStyle(c, pin)(displayLabel(pin).padEnd(labelWidth));
108
- lines.push(`${INDENT}${ch.vertical} ${String(pin.physical).padStart(numWidth)} ${ch.vertical} ${label} ${ch.vertical} ${pinDetails(pin).padEnd(detailWidth)} ${ch.vertical}`);
122
+ const label = pinStyle(c, pin)(padEndVisible(displayLabel(pin, ch), labelWidth));
123
+ lines.push(`${INDENT}${ch.vertical} ${String(pin.physical).padStart(numWidth)} ${ch.vertical} ${label} ${ch.vertical} ${padEndVisible(pinDetails(pin), detailWidth)} ${ch.vertical}`);
124
+ }
125
+ lines.push(`${INDENT}${ch.bottomLeft}${segment(numWidth)}${ch.bottomJoin}${segment(labelWidth)}${ch.bottomJoin}${segment(detailWidth)}${ch.bottomRight}`);
126
+ return lines;
127
+ }
128
+ function renderPinAnnotations(header, opts) {
129
+ const { chalk: c, chars: ch } = opts;
130
+ const width = renderWidth(opts);
131
+ const pins = [...header.pins].sort((left, right) => left.physical - right.physical);
132
+ const annotated = pins.filter((pin) => Boolean(pin.notes?.length) || (Boolean(opts.details) && (pin.functions?.length ?? 0) > 1));
133
+ const lines = [];
134
+ if (annotated.length > 0) {
135
+ lines.push(c.bold("Pin notes"));
136
+ for (const pin of annotated) {
137
+ const details = [];
138
+ if (opts.details && (pin.functions?.length ?? 0) > 1) {
139
+ details.push(`additional functions: ${pin.functions.slice(1).join(", ")}`);
140
+ }
141
+ details.push(...(pin.notes ?? []));
142
+ const prefix = `${INDENT}${ch.note ?? "^"} Pin ${pin.physical}: `;
143
+ const continuation = " ".repeat(visibleWidth(prefix));
144
+ lines.push(...wrapText(details.join("; "), width, prefix, continuation));
145
+ }
146
+ }
147
+ if (!opts.details) {
148
+ const hiddenAlternates = pins.filter((pin) => (pin.functions?.length ?? 0) > 1).length;
149
+ if (hiddenAlternates > 0) {
150
+ if (lines.length > 0)
151
+ lines.push("");
152
+ lines.push(...wrapText(`${ch.info} ${hiddenAlternates} pin${hiddenAlternates === 1 ? " has" : "s have"} additional alternate functions; use --details to show them.`, width, INDENT, `${INDENT} `).map((line) => c.dim(line)));
153
+ }
109
154
  }
110
- lines.push(`${INDENT}${ch.bottomLeft}${seg(numWidth)}${ch.bottomJoin}${seg(labelWidth)}${ch.bottomJoin}${seg(detailWidth)}${ch.bottomRight}`);
111
155
  return lines;
112
156
  }
113
157
  function renderHeader(header, opts) {
114
158
  const { chalk: c } = opts;
115
- const lines = [];
116
- lines.push(c.bold(header.name));
159
+ const width = renderWidth(opts);
160
+ const lines = wrapText(header.name, width).map((line) => c.bold(line));
117
161
  if (header.description) {
118
- lines.push(...wrapText(header.description, opts.width, INDENT).map((l) => c.dim(l)));
162
+ lines.push(...wrapText(header.description, width, INDENT).map((line) => c.dim(line)));
119
163
  }
120
164
  let table;
121
165
  if (header.layout.columns === 2) {
122
166
  table = opts.compact
123
- ? renderDualRowCompact(header, opts) ?? renderPinList(header, opts)
124
- : renderDualRowTable(header, opts) ??
167
+ ? (renderDualRowCompact(header, opts) ?? renderPinList(header, opts))
168
+ : (renderDualRowTable(header, opts) ??
125
169
  renderDualRowCompact(header, opts) ??
126
- renderPinList(header, opts);
170
+ renderPinList(header, opts));
127
171
  }
128
172
  else {
129
- table = opts.compact ? renderPinList(header, opts) : renderSingleColumnTable(header, opts) ?? renderPinList(header, opts);
173
+ table = opts.compact
174
+ ? renderPinList(header, opts)
175
+ : (renderSingleColumnTable(header, opts) ?? renderPinList(header, opts));
130
176
  }
131
177
  lines.push(...table);
178
+ const annotations = renderPinAnnotations(header, opts);
179
+ if (annotations.length > 0)
180
+ lines.push("", ...annotations);
132
181
  return lines;
133
182
  }
134
183
  function renderLegend(board, opts) {
135
- const { chalk: c } = opts;
136
- if (c.level === 0)
137
- return [];
184
+ const { chalk: c, chars: ch } = opts;
138
185
  const order = [
139
186
  "power",
140
187
  "ground",
@@ -145,39 +192,68 @@ function renderLegend(board, opts) {
145
192
  "reserved",
146
193
  "nc",
147
194
  ];
148
- const present = order.filter((cat) => board.headers.some((h) => h.pins.some((p) => p.category === cat)));
149
- const swatches = present.map((cat) => {
195
+ const present = order.filter((category) => board.headers.some((header) => header.pins.some((pin) => pin.category === category)));
196
+ if (present.length === 0)
197
+ return [];
198
+ const swatches = present.map((category) => {
150
199
  const sample = board.headers
151
- .flatMap((h) => h.pins)
152
- .find((p) => p.category === cat);
153
- return pinStyle(c, sample)(cat);
200
+ .flatMap((header) => header.pins)
201
+ .find((pin) => pin.category === category);
202
+ return c.level === 0 ? category : pinStyle(c, sample)(category);
154
203
  });
155
- return [c.dim("Legend: ") + swatches.join(c.dim(" · "))];
204
+ return wrapText(`Legend: ${swatches.join(` ${ch.itemSeparator ?? "|"} `)}`, renderWidth(opts)).map((line) => c.dim(line));
205
+ }
206
+ function warningMarker(ch, severity) {
207
+ switch (severity) {
208
+ case "danger":
209
+ return ch.danger ?? ch.warn;
210
+ case "warning":
211
+ return ch.warn;
212
+ case "info":
213
+ return ch.info;
214
+ }
156
215
  }
157
216
  export function renderWarnings(board, opts) {
158
217
  const { chalk: c, chars: ch } = opts;
159
218
  return board.warnings.flatMap((warning) => {
160
- const marker = warning.severity === "info" ? ch.info : ch.warn;
219
+ const marker = warningMarker(ch, warning.severity);
161
220
  const style = warningStyle(c, warning.severity);
162
- return wrapText(`${marker} ${warning.text}`, opts.width, "").map((line, i) => style(i === 0 ? line : ` ${line}`));
221
+ return wrapText(`${marker} ${warning.text}`, renderWidth(opts), "", INDENT).map((line) => style(line));
163
222
  });
164
223
  }
224
+ function primarySourceFor(board) {
225
+ const typeScore = {
226
+ Pinout: 2000,
227
+ Schematic: 800,
228
+ Datasheet: 70,
229
+ Manual: 60,
230
+ Docs: 50,
231
+ };
232
+ return board.sources.reduce((best, source) => {
233
+ if (!best)
234
+ return source;
235
+ const score = (source.official ? 1000 : 0) + (source.type ? typeScore[source.type] : 0);
236
+ const bestScore = (best.official ? 1000 : 0) + (best.type ? typeScore[best.type] : 0);
237
+ return score > bestScore ? source : best;
238
+ }, undefined);
239
+ }
165
240
  export function renderBoard(board, opts) {
166
241
  const { chalk: c, chars: ch } = opts;
242
+ const width = renderWidth(opts);
167
243
  const lines = [];
168
- lines.push(`${c.bold(board.name)} ${c.dim(`— ${board.manufacturer}`)}`);
169
- const primarySource = board.sources.find((s) => s.official) ?? board.sources[0];
244
+ lines.push(...wrapText(`${board.name} ${ch.dash ?? "-"} ${board.manufacturer}`, width).map((line) => c.bold(line)));
245
+ const primarySource = primarySourceFor(board);
170
246
  if (primarySource) {
171
247
  const tag = primarySource.official ? "official" : "third-party";
172
- lines.push(c.dim(`Source: ${primarySource.title} (${tag}) see ph ${board.id} --source`));
248
+ lines.push(...wrapText(`Source: ${primarySource.title} (${tag}) ${ch.dash ?? "-"} see ph ${board.id} --source`, width, "", INDENT).map((line) => c.dim(line)));
173
249
  }
174
250
  if (board.revisionNote) {
175
- lines.push(...wrapText(`${ch.info} ${board.revisionNote}`, opts.width, "").map((line, i) => c.dim(i === 0 ? line : ` ${line}`)));
251
+ lines.push(...wrapText(`${ch.info} ${board.revisionNote}`, width, "", INDENT).map((line) => c.dim(line)));
176
252
  }
177
253
  lines.push(...renderWarnings(board, opts));
178
254
  lines.push("");
179
255
  if (board.headers.length === 0) {
180
- lines.push(c.dim(`No pinout data for this board yet see ph ${board.id} --source for documentation.`));
256
+ lines.push(...wrapText(`No pinout data for this board yet ${ch.dash ?? "-"} see ph ${board.id} --source for documentation.`, width).map((line) => c.dim(line)));
181
257
  }
182
258
  board.headers.forEach((header, index) => {
183
259
  if (index > 0)
@@ -185,13 +261,15 @@ export function renderBoard(board, opts) {
185
261
  lines.push(...renderHeader(header, opts));
186
262
  });
187
263
  const legend = renderLegend(board, opts);
188
- if (legend.length > 0) {
189
- lines.push("");
190
- lines.push(...legend);
191
- }
192
- const hasReserved = board.headers.some((h) => h.pins.some((p) => p.category === "reserved"));
264
+ if (legend.length > 0)
265
+ lines.push("", ...legend);
266
+ const hasReserved = board.headers.some((header) => header.pins.some((pin) => pin.category === "reserved"));
193
267
  if (hasReserved) {
194
- lines.push(c.dim("* reserved / control pin see warnings above"));
268
+ lines.push(...wrapText(`* reserved / control pin ${ch.dash ?? "-"} see warnings above`, width).map((line) => c.dim(line)));
269
+ }
270
+ const hasNotes = board.headers.some((header) => header.pins.some((pin) => pin.notes?.length));
271
+ if (hasNotes) {
272
+ lines.push(...wrapText(`${ch.note ?? "^"} pin-specific note`, width).map((line) => c.dim(line)));
195
273
  }
196
- return lines.join("\n");
274
+ return finish(lines.join("\n"), opts);
197
275
  }
@@ -7,10 +7,14 @@ export type CharSet = {
7
7
  vertical: string;
8
8
  topJoin: string;
9
9
  bottomJoin: string;
10
+ danger?: string;
10
11
  warn: string;
11
12
  info: string;
13
+ note?: string;
12
14
  bullet: string;
13
15
  pairSeparator: string;
16
+ dash?: string;
17
+ itemSeparator?: string;
14
18
  };
15
19
  export declare const unicodeChars: CharSet;
16
20
  export declare const asciiChars: CharSet;
@@ -7,10 +7,14 @@ export const unicodeChars = {
7
7
  vertical: "│",
8
8
  topJoin: "┬",
9
9
  bottomJoin: "┴",
10
+ danger: "✖",
10
11
  warn: "⚠",
11
12
  info: "ℹ",
13
+ note: "†",
12
14
  bullet: "•",
13
15
  pairSeparator: "│",
16
+ dash: "—",
17
+ itemSeparator: "·",
14
18
  };
15
19
  export const asciiChars = {
16
20
  topLeft: "+",
@@ -21,8 +25,12 @@ export const asciiChars = {
21
25
  vertical: "|",
22
26
  topJoin: "+",
23
27
  bottomJoin: "+",
28
+ danger: "X",
24
29
  warn: "!",
25
30
  info: "i",
31
+ note: "^",
26
32
  bullet: "*",
27
33
  pairSeparator: "|",
34
+ dash: "-",
35
+ itemSeparator: ".",
28
36
  };
@@ -1,6 +1,6 @@
1
- import type { ChalkInstance } from "chalk";
2
1
  import type { Board } from "../model.js";
3
- import type { CharSet } from "./chars.js";
4
- export declare function renderBoardTable(boardList: Board[], c: ChalkInstance): string;
5
- export declare function renderSources(board: Board, c: ChalkInstance): string;
6
- export declare function renderInfo(board: Board, c: ChalkInstance, ch: CharSet, width: number): string;
2
+ import type { RenderOptions } from "./board.js";
3
+ /** Width-responsive catalog/search table; cells wrap instead of being truncated. */
4
+ export declare function renderBoardTable(boardList: Board[], opts: RenderOptions): string;
5
+ export declare function renderSources(board: Board, opts: RenderOptions): string;
6
+ export declare function renderInfo(board: Board, opts: RenderOptions): string;