** Generates snippets of source code, often used to report errors. Example:
**
** pre>
** file:/C:/test/app/compilationErr.moustache : Line 11
** - Unbalanced "{" in tag "{ alienHeadSvg } <span class="brand">{{ title"
**
** 6: {{{ bedSheetCss }}}
** 7: </style>
** 8: </head>
** 9: <body>
** 10: <header>
** ==> 11: {{{ alienHeadSvg }
** 12: <span class="brand">{{ title }}</span>
** 13: </header>
** 14:
** 15: <main>
** 16: {{{ content }}}
** <pre
const class SrcCodeSnippet {
** An arbitrary uri of where the source code originated from.
const Uri srcCodeLocation
** A list of source code lines.
const Str[] srcCode
** Creates a SrcCodeSnippet.
** The srcCodeLocation URI may be anything as it is only used for reporting.
new make(Uri srcCodeLocation, Str srcCode) {
this.srcCodeLocation= srcCodeLocation
this.srcCode = srcCode.splitLines
}
** Returns a snippet of source code, centred on 'lineNo' and padded on either side by an
** extra 'linesOfPadding'.
**
** Note that 'lineNo' is ONE based, not zero.
Str srcCodeSnippet(Int lineNo, Str? msg := null, Int linesOfPadding := 5) {
buf := StrBuf()
buf.add(" ${srcCodeLocation}").add(" : Line ${lineNo}\n")
if (msg != null)
buf.add(" - ${msg}\n")
buf.add("\n")
srcCodeSnippetMap(lineNo, linesOfPadding).each |src, line| {
pointer := (line == lineNo) ? "==>" : " "
buf.add("${pointer}${line.toStr.justr(3)}: ${src}\n".replace("\t", " "))
}
return buf.toStr
}
** Returns a map of line numbers to source code, centred on 'lineNo' and padded on either
** side by an extra 'linesOfPadding'.
**
** Note that 'lineNo' is ONE based, not zero.
Int:Str srcCodeSnippetMap(Int lineNo, Int linesOfPadding := 5) {
min := (lineNo - 1 - linesOfPadding).max(0) // -1 so "Line 1" == src[0]
max := (lineNo - 1 + linesOfPadding + 1).min(srcCode.size)
lines := Int:Str[:] { it.ordered = true }
(min..<max).each { lines[it+1] = srcCode[it] }
// uniformly remove extra whitespace
while (canTrim(lines))
trim(lines)
return lines
}
private Bool canTrim(Int:Str lines) {
lines.vals.all { it.size > 0 && it[0].isSpace }
}
private Void trim(Int:Str lines) {
lines.each |val, key| { lines[key] = val[1..-1] }
}
@NoDoc
override Str toStr() {
"${srcCodeLocation} : ${srcCode.size} lines"
}
}