///|
/// A formatter class: its metadata and constructor (`options`, optional
/// style overriding the `style` option).
pub type FormatterClass = (
FormatterInfo,
(@lexer.Options, @styles.Style?) -> Formatter raise,
)
///|
/// All formatters, in Python's `FORMATTERS` order. The PIL image formatters
/// `img`, `gif`, `jpg` and `bmp` are listed but raise when constructed.
let all_formatters : Array[FormatterClass] = [
(bbcode_info, (o, s) => bbcode_formatter(options=o, style?=s)),
(image_formatter_infos[0], image_formatter),
(image_formatter_infos[1], image_formatter),
(groff_info, (o, s) => groff_formatter(options=o, style?=s)),
(html_info, (o, s) => HtmlFormatter::new(options=o, style?=s).to_formatter()),
(irc_info, (o, s) => irc_formatter(options=o, style?=s)),
(image_formatter_infos[2], image_formatter),
(image_formatter_infos[3], image_formatter),
(
latex_info,
(o, s) => LatexFormatter::new(options=o, style?=s).to_formatter(),
),
(null_info, (o, s) => null_formatter(options=o, style?=s)),
(pango_info, (o, s) => pango_markup_formatter(options=o, style?=s)),
(raw_token_info, (o, s) => raw_token_formatter(options=o, style?=s)),
(rtf_info, (o, s) => rtf_formatter(options=o, style?=s)),
(svg_info, (o, s) => svg_formatter(options=o, style?=s)),
(terminal256_info, (o, s) => terminal256_formatter(options=o, style?=s)),
(terminal_info, (o, s) => terminal_formatter(options=o, style?=s)),
(
terminal_true_color_info,
(o, s) => terminal_true_color_formatter(options=o, style?=s),
),
(testcase_info, (o, s) => testcase_formatter(options=o, style?=s)),
]
///|
/// Python's `get_all_formatters`: metadata of every formatter.
pub fn get_all_formatters() -> Array[FormatterInfo] {
all_formatters.map(c => c.0)
}
///|
/// Python's `find_formatter_class`: the formatter class with `alias`.
pub fn find_formatter_class(name : String) -> FormatterClass? {
for c in all_formatters {
if c.0.aliases.contains(name) {
return Some(c)
}
}
None
}
///|
/// Python's `get_formatter_by_name`: an instance of the formatter with
/// `alias`, configured with `options`.
///
/// ```mbt check
/// test {
/// let f = @formatters.get_formatter_by_name("html", options={ "nowrap": "true" })
/// inspect(
/// f.format([(@token.keyword, "if")]),
/// content="if\n",
/// )
/// }
/// ```
pub fn get_formatter_by_name(
name : String,
options? : @lexer.Options = Map([]),
style? : @styles.Style,
) -> Formatter raise {
match find_formatter_class(name) {
Some(c) => (c.1)(options, style)
None =>
raise ClassNotFound("no formatter found for name \{@pystr.repr(name)}")
}
}
///|
/// Python's `fnmatch.fnmatch` (case-sensitive; `*`, `?`, `[...]`).
fn fn_matches(name : Array[Char], pat : Array[Char], i : Int, j : Int) -> Bool {
if j == pat.length() {
return i == name.length()
}
match pat[j] {
'*' => {
for k = i; k <= name.length(); k = k + 1 {
if fn_matches(name, pat, k, j + 1) {
return true
}
}
false
}
'?' => i < name.length() && fn_matches(name, pat, i + 1, j + 1)
'[' => {
let mut k = j + 1
let negate = k < pat.length() && pat[k] == '!'
if negate {
k += 1
}
let first = k
while k < pat.length() && (pat[k] != ']' || k == first) {
k += 1
}
if k >= pat.length() {
// no closing bracket: a literal '['
return i < name.length() &&
name[i] == '[' &&
fn_matches(name, pat, i + 1, j + 1)
}
if i >= name.length() {
return false
}
let c = name[i]
let mut hit = false
let mut m = first
while m < k {
if m + 2 < k && pat[m + 1] == '-' {
if c >= pat[m] && c <= pat[m + 2] {
hit = true
}
m += 3
} else {
if c == pat[m] {
hit = true
}
m += 1
}
}
hit != negate && fn_matches(name, pat, i + 1, k + 1)
}
ch =>
i < name.length() && name[i] == ch && fn_matches(name, pat, i + 1, j + 1)
}
}
///|
/// Python's `get_formatter_for_filename`: an instance of the formatter
/// whose file name patterns match the base name of `filename`.
///
/// ```mbt check
/// test {
/// inspect(
/// @formatters.get_formatter_for_filename("out/a.tex").name(),
/// content="LaTeX",
/// )
/// }
/// ```
pub fn get_formatter_for_filename(
filename : String,
options? : @lexer.Options = Map([]),
style? : @styles.Style,
) -> Formatter raise {
let base = match filename.rev_find("/") {
Some(i) => filename.unsafe_substring(start=i + 1, end=filename.length())
None => filename
}
let name = base.to_array()
for c in all_formatters {
for pat in c.0.filenames {
if fn_matches(name, pat.to_array(), 0, 0) {
return (c.1)(options, style)
}
}
}
raise ClassNotFound("no formatter found for file name \{@pystr.repr(base)}")
}