Skip to content

Commit 3c8121f

Browse files
committed
feat: allow returning specific sections only in HTML
1 parent 0e9c4e0 commit 3c8121f

4 files changed

Lines changed: 35 additions & 10 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ const result = friendlyExplain({
4141

4242
// result.html is a ready-made snippet
4343
// or use result.title, result.summary, result.steps, result.patch, result.trace
44+
45+
// optionally limit which sections appear in result.html:
46+
const result = friendlyExplain({
47+
error: rawTracebackString,
48+
code: editorCode,
49+
runtime: "skulpt",
50+
sections: ["title", "summary"] // "why", "steps", "patch", "details" also available
51+
});
4452
```
4553

4654
See the [demo](docs/README.md) for a full set of examples.

src/engine.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { CopyDeck, ExplainOptions, ExplainResult, Trace } from "./types";
1+
import type { CopyDeck, ExplainOptions, ExplainResult, Section, Trace } from "./types";
22
import { escapeHtml, safeRegexTest, tmpl } from "./utils";
33

44
type InternalState = {
@@ -37,7 +37,7 @@ const coerceTrace = (input: string | Error | Trace, code?: string, runtime?: str
3737
return parsed;
3838
};
3939

40-
const pickVariant = (trace: Trace, code: string | undefined) => {
40+
const pickVariant = (trace: Trace, code: string | undefined, sections?: Section[]) => {
4141
const deck = state.copy;
4242
const kind = trace.type && deck?.errors[trace.type] ? trace.type : "Other";
4343
const entry = deck?.errors[kind];
@@ -111,15 +111,16 @@ const pickVariant = (trace: Trace, code: string | undefined) => {
111111
patch = codeLine.replace(/\+\s*([A-Za-z_][A-Za-z0-9_]*)/, "+ str($1)");
112112
}
113113

114+
const has = (s: Section) => !sections || sections.includes(s);
114115
const html = [
115-
`<div class="pfem__title">${titleHtml}</div>`,
116-
`<div class="pfem__summary">${summaryHtml}</div>`,
117-
whyHtml ? `<div class="pfem__why">${whyHtml}</div>` : "",
118-
stepsHtml?.length ? `<ul class="pfem__steps">${stepsHtml.map((s) => `<li>${s}</li>`).join("")}</ul>` : "",
119-
patch ? `<pre class="pfem__patch">${escapeHtml(patch)}</pre>` : "",
120-
`<details class="pfem__details"><summary>${escapeHtml(getUiString("originalError", "Original error"))}</summary><pre>${escapeHtml(
116+
has("title") ? `<div class="pfem__title">${titleHtml}</div>` : "",
117+
has("summary") ? `<div class="pfem__summary">${summaryHtml}</div>` : "",
118+
has("why") && whyHtml ? `<div class="pfem__why">${whyHtml}</div>` : "",
119+
has("steps") && stepsHtml?.length ? `<ul class="pfem__steps">${stepsHtml.map((s) => `<li>${s}</li>`).join("")}</ul>` : "",
120+
has("patch") && patch ? `<pre class="pfem__patch">${escapeHtml(patch)}</pre>` : "",
121+
has("details") ? `<details class="pfem__details"><summary>${escapeHtml(getUiString("originalError", "Original error"))}</summary><pre>${escapeHtml(
121122
(trace.type || getUiString("error", "Error")) + ": " + trace.message
122-
)}</pre></details>`
123+
)}</pre></details>` : "",
123124
].filter(Boolean).join("\n");
124125

125126
return {
@@ -146,7 +147,7 @@ export const friendlyExplain = (opts: ExplainOptions): ExplainResult => {
146147
trace.codeLine = lines[trace.line - 1]?.trim();
147148
}
148149

149-
const chosen = pickVariant(trace, code);
150+
const chosen = pickVariant(trace, code, opts.sections);
150151
if (!chosen) {
151152
return {
152153
trace,

src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ export type Trace = {
1717
version?: string;
1818
};
1919

20+
export type Section = "title" | "summary" | "why" | "steps" | "patch" | "details";
21+
2022
export type ExplainOptions = {
2123
error: string | Error | Trace;
2224
code?: string;
2325
locale?: string;
2426
runtime?: string;
27+
sections?: Section[];
2528
};
2629

2730
export type ExplainResult = {

tests/engine.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,19 @@ NameError: name 'kittens' is not defined`;
138138
expect(res.html).toContain('<span class="pfem__file">main.py</span>');
139139
});
140140

141+
it("sections option limits html output to specified sections", () => {
142+
const code = `print("Hello")\nprint(kittens)\n`;
143+
const raw = `Traceback (most recent call last):
144+
File "main.py", line 2, in <module>
145+
NameError: name 'kittens' is not defined`;
146+
const res = friendlyExplain({ error: raw, code, runtime: "skulpt", sections: ["title", "summary"] });
147+
expect(res.html).toContain("pfem__title");
148+
expect(res.html).toContain("pfem__summary");
149+
expect(res.html).not.toContain("pfem__why");
150+
expect(res.html).not.toContain("pfem__steps");
151+
expect(res.html).not.toContain("pfem__details");
152+
});
153+
141154
it("escapes HTML in codeLine within html output", () => {
142155
const code = `for i in range(3)<script>alert(1)</script>\n print(i)`;
143156
const raw = `Traceback (most recent call last):

0 commit comments

Comments
 (0)