/** * A word whose value is text with provenance- this literal value appeared in the source * code, and was not the result of any backslash, variable, or command substitutions. * * 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 * puts $var text ;# The value of $var is part of the message to print, even if the value happens to be "-stderr" * ``` */ export type EnchantedWord = { enchanted: string; }; /** * 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; }; export type TextPiece = TextWord; export type VariablePiece = { variable: string }; export type CommandPiece = { command: unknown }; export type InterpolatedPiece = TextPiece | VariablePiece | CommandPiece; /** * 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 = { pieces: InterpolatedPiece[]; }; function IsTextPiece(piece: InterpolatedPiece | undefined): piece is TextPiece { return piece ? "text" in piece : false; } // safely concatenate text pieces, converting as needed export function Concat(left: TextPiece, right: TextPiece) { return { text: left.text + right.text }; } export function SimplifyWord( pieces: InterpolatedPiece[] ): InterpolatedWord | TextWord { 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 }; } } export type Word = EnchantedWord | TextWord | HtmlWord | InterpolatedWord;