2023-10-20 23:06:01 +00:00
|
|
|
import { escapeHtml } from "./helpers";
|
2023-08-26 05:16:22 +00:00
|
|
|
|
2023-08-07 03:18:13 +00:00
|
|
|
/**
|
2023-10-19 00:06:52 +00:00
|
|
|
* A word whose value is text with provenance- this literal value appeared in the source code,
|
|
|
|
* and was neither quoted nor the result of any backslash, variable, or command substitutions.
|
2023-08-07 03:18:13 +00:00
|
|
|
*
|
|
|
|
* This provides a level of intentionality that commands can use to distinguish switches:
|
|
|
|
*
|
|
|
|
* ```tcl
|
|
|
|
* puts -stderr text ;# -stderr can be interpreted as a flag and is not part of the message to print
|
|
|
|
* puts "-stderr" text ;# -stderr is not a flag, but is part of the message to print
|
2023-09-01 02:58:44 +00:00
|
|
|
* puts [return -stderr] text ;# -stderr is not a flag, but is part of the message to print
|
2023-08-07 03:18:13 +00:00
|
|
|
* puts $var text ;# The value of $var is part of the message to print, even if the value happens to be "-stderr"
|
|
|
|
* ```
|
|
|
|
*/
|
2023-10-19 00:06:52 +00:00
|
|
|
export type BareWord = {
|
|
|
|
bare: string;
|
2023-08-07 03:18:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A word whose value is plain text, with no special provenance.
|
|
|
|
*/
|
|
|
|
export type TextWord = {
|
|
|
|
text: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A word whose value is "safe" HTML using allowlisted elements/attributes/styles,
|
|
|
|
* suitable for inclusion in HTML output with no escaping.
|
|
|
|
*/
|
|
|
|
export type HtmlWord = {
|
|
|
|
html: string;
|
|
|
|
};
|
|
|
|
|
2023-10-20 23:06:01 +00:00
|
|
|
export type ErrorResult = {
|
|
|
|
error: string;
|
|
|
|
};
|
|
|
|
|
2023-10-19 00:06:52 +00:00
|
|
|
export type TextPiece = BareWord | TextWord | HtmlWord;
|
2023-10-20 23:06:01 +00:00
|
|
|
|
2023-08-23 05:09:56 +00:00
|
|
|
export type VariablePiece = { variable: string };
|
2023-08-25 23:10:45 +00:00
|
|
|
export type ScriptPiece = { script: Script };
|
|
|
|
export type InterpolatedPiece = TextPiece | VariablePiece | ScriptPiece;
|
2023-08-23 05:09:56 +00:00
|
|
|
|
2023-10-20 23:06:01 +00:00
|
|
|
export type ProcResult = TextPiece | ErrorResult;
|
|
|
|
|
2023-08-07 03:18:13 +00:00
|
|
|
/**
|
|
|
|
* A word whose value needs to be determined by evaluating some combination of variable and command
|
|
|
|
* substitutions, and concatenating the results with any literal spans.
|
|
|
|
*/
|
|
|
|
export type InterpolatedWord = {
|
2023-08-23 05:09:56 +00:00
|
|
|
pieces: InterpolatedPiece[];
|
2023-08-07 03:18:13 +00:00
|
|
|
};
|
|
|
|
|
2023-08-26 05:16:22 +00:00
|
|
|
export function AsText(word: TextPiece): string {
|
2023-10-19 00:06:52 +00:00
|
|
|
if ("bare" in word) {
|
|
|
|
return word.bare;
|
2023-08-26 05:16:22 +00:00
|
|
|
} else if ("text" in word) {
|
|
|
|
return word.text;
|
|
|
|
} else if ("html" in word) {
|
|
|
|
return word.html;
|
|
|
|
} else {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
2023-10-20 23:06:01 +00:00
|
|
|
export function AsHtml(word: TextPiece | ErrorResult): string {
|
2023-10-19 00:06:52 +00:00
|
|
|
if ("bare" in word) {
|
|
|
|
return escapeHtml(word.bare);
|
2023-08-26 05:16:22 +00:00
|
|
|
} else if ("text" in word) {
|
|
|
|
return escapeHtml(word.text);
|
|
|
|
} else if ("html" in word) {
|
|
|
|
return word.html;
|
2023-10-20 23:06:01 +00:00
|
|
|
} else if ("error" in word) {
|
|
|
|
return '<span style="color: red;">' + escapeHtml(word.error) + "</span>";
|
2023-08-26 05:16:22 +00:00
|
|
|
} else {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-23 05:09:56 +00:00
|
|
|
// safely concatenate text pieces, converting as needed
|
2023-08-26 05:16:22 +00:00
|
|
|
export function Concat(left: TextPiece | null, right: TextPiece) {
|
|
|
|
if (left === null) {
|
2023-10-19 00:06:52 +00:00
|
|
|
return "bare" in right ? { text: right.bare } : right;
|
2023-08-26 05:16:22 +00:00
|
|
|
}
|
|
|
|
if ("html" in left || "html" in right) {
|
|
|
|
return { html: AsHtml(left) + AsHtml(right) };
|
|
|
|
} else {
|
|
|
|
return { text: AsText(left) + AsText(right) };
|
|
|
|
}
|
2023-08-23 05:09:56 +00:00
|
|
|
}
|
|
|
|
|
2023-08-25 15:13:00 +00:00
|
|
|
function IsTextPiece(piece: InterpolatedPiece | undefined): piece is TextPiece {
|
2023-10-19 00:06:52 +00:00
|
|
|
return piece ? "text" in piece || "bare" in piece || "html" in piece : false;
|
2023-08-25 15:13:00 +00:00
|
|
|
}
|
|
|
|
|
2023-08-23 05:09:56 +00:00
|
|
|
export function SimplifyWord(
|
|
|
|
pieces: InterpolatedPiece[]
|
2023-10-19 00:06:52 +00:00
|
|
|
): InterpolatedWord | BareWord | TextWord | HtmlWord {
|
2023-08-23 05:09:56 +00:00
|
|
|
const consolidated: InterpolatedPiece[] = [];
|
|
|
|
for (const piece of pieces) {
|
|
|
|
const top = consolidated[consolidated.length - 1];
|
|
|
|
if (IsTextPiece(top) && IsTextPiece(piece)) {
|
|
|
|
consolidated[consolidated.length - 1] = Concat(top, piece);
|
|
|
|
} else {
|
|
|
|
consolidated.push(piece);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (consolidated.length == 0) {
|
|
|
|
return { text: "" };
|
|
|
|
} else if (consolidated.length == 1 && IsTextPiece(consolidated[0])) {
|
|
|
|
return consolidated[0];
|
|
|
|
} else {
|
|
|
|
return { pieces: consolidated };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-19 00:06:52 +00:00
|
|
|
export type Word = BareWord | TextWord | HtmlWord | InterpolatedWord;
|
2023-08-25 16:05:06 +00:00
|
|
|
export type Command = Word[];
|
|
|
|
export type Script = Command[];
|