///|
/// Metadata of Python's `GroffFormatter`.
pub let groff_info : FormatterInfo = {
class_name: "GroffFormatter",
name: "groff",
aliases: ["groff", "troff", "roff"],
filenames: [],
description: "Format tokens with groff escapes to change their color and font style.",
}
///|
/// Python's `GroffFormatter._escape_chars`.
fn groff_escape(text : String) -> String {
let buf = StringBuilder()
for c in text {
match c {
'\\' => buf.write_string("\\[u005C]")
'.' => buf.write_string("\\[char46]")
'\'' => buf.write_string("\\[u0027]")
'`' => buf.write_string("\\[u0060]")
'~' => buf.write_string("\\[u007E]")
_ => {
let cp = c.to_int()
if cp < 0x80 {
buf.write_char(c)
} else {
let width = if cp < 0x10000 { 4 } else { 8 }
let hex = @pystr.upper(
@pystr.rjust(cp.to_string(radix=16), width, fill='0'),
)
buf.write_string("\\[u\{hex}]")
}
}
}
}
buf.to_string()
}
///|
/// Python's `GroffFormatter`. Options: `style`, `monospaced`, `linenos`,
/// `wrap`. As in Python, the line counter and the current line length
/// continue across `format` calls.
pub fn groff_formatter(
options? : @lexer.Options = Map([]),
style? : @styles.Style,
) -> Formatter raise {
let base = BaseOptions::new(options, style?)
let monospaced = @lexer.get_bool_opt(options, "monospaced", true)
let linenos = @lexer.get_bool_opt(options, "linenos", false)
let wrap = @lexer.get_int_opt(options, "wrap", 0)
let lineno = Ref(0)
let linelen = Ref(0)
let regular = if monospaced { "\\f[CR]" } else { "\\f[R]" }
let bold = if monospaced { "\\f[CB]" } else { "\\f[B]" }
let italic = if monospaced { "\\f[CI]" } else { "\\f[I]" }
let styles : Map[@token.TokenType, (String, String)] = Map([])
for entry in base.style.iter() {
let (ttype, ndef) = entry
let mut start = ""
let mut end = ""
if ndef.color is Some(c) {
start += "\\m[\{c}]"
end = "\\m[]" + end
}
if ndef.bold {
start += bold
end = regular + end
}
if ndef.italic {
start += italic
end = regular + end
}
if ndef.bgcolor is Some(c) {
start += "\\M[\{c}]"
end = "\\M[]" + end
}
styles[ttype] = (start, end)
}
let write_lineno = (buf : StringBuilder) => {
lineno.val += 1
buf.write_string(if lineno.val != 1 { "\n" } else { "" })
buf.write_string(@pystr.rjust(" " + lineno.val.to_string(), 4))
buf.write_string(" ")
}
// Python's `_wrap_line`
let wrap_line = (line : String) => {
let length = @pystr.rstrip_newlines(line).char_length()
let space = if linenos { " " } else { "" }
if length > wrap {
let buf = StringBuilder()
for i in 0..<(length / wrap) {
buf.write_string(@pystr.slice(line, i * wrap, end=i * wrap + wrap))
buf.write_string("\n" + space)
}
let remainder = length % wrap
if remainder > 0 {
buf.write_string(@pystr.slice(line, length - remainder))
linelen.val = remainder
}
buf.to_string()
} else if linelen.val + length > wrap {
linelen.val = length
"\n" + space + line
} else {
linelen.val += length
line
}
}
Formatter::new(groff_info, base, tokens => {
let buf = StringBuilder()
// Python's `_define_colors`
let colors = []
for entry in base.style.iter() {
if entry.1.color is Some(c) && !colors.contains(c) {
colors.push(c)
}
}
colors.sort_by(py_str_compare)
for c in colors {
buf.write_string(".defcolor \{c} rgb #\{c}\n")
}
buf.write_string(".nf\n\\f[CR]\n")
if linenos {
write_lineno(buf)
}
for tok in tokens {
let t = nearest_styled(styles, tok.0)
let (start, end) = styles.get(t).unwrap_or(("", ""))
for line in @pystr.splitlines(tok.1, keepends=true) {
let line = if wrap > 0 { wrap_line(line) } else { line }
if start != "" && end != "" {
let text = groff_escape(@pystr.rstrip_newlines(line))
if text != "" {
buf.write_string(start + text + end)
}
} else {
buf.write_string(groff_escape(@pystr.rstrip_newlines(line)))
}
if line.has_suffix("\n") {
if linenos {
write_lineno(buf)
} else {
buf.write_string("\n")
}
linelen.val = 0
}
}
}
buf.write_string("\n.fi")
buf.to_string()
})
}