///|
/// Parsed command line of `pygmentize` (mirrors Python's argparse setup in
/// `pygments/cmdline.py`).
priv struct Args {
mut lexer : String?
mut guess : Bool
filters : Array[String]
mut formatter : String?
options : Array[String]
single_options : Array[String]
mut output : String?
mut input : String?
mut verbose : Bool
mut stream : Bool
mut custom : Bool
mut json : Bool
mut style : String?
mut list : Array[String]?
mut name_for_file : String?
mut guess_stdin : Bool
mut help_on : (String, String)?
mut version : Bool
mut help : Bool
mut style_arg : String?
}
///|
priv suberror UsageError {
UsageError(String)
}
///|
fn Args::new() -> Args {
{
lexer: None,
guess: false,
filters: [],
formatter: None,
options: [],
single_options: [],
output: None,
input: None,
verbose: false,
stream: false,
custom: false,
json: false,
style: None,
list: None,
name_for_file: None,
guess_stdin: false,
help_on: None,
version: false,
help: false,
style_arg: None,
}
}
///|
/// Short options that take a value.
fn takes_value(c : Char) -> Bool {
match c {
'l' | 'F' | 'f' | 'O' | 'P' | 'o' | 'S' | 'N' | 'a' => true
_ => false
}
}
///|
fn Args::set_value(self : Args, c : Char, v : String) -> Unit {
match c {
'l' => self.lexer = Some(v)
'F' => self.filters.push(v)
'f' => self.formatter = Some(v)
'O' => self.options.push(v)
'P' => self.single_options.push(v)
'o' => self.output = Some(v)
'S' => self.style = Some(v)
'N' => self.name_for_file = Some(v)
_ => self.style_arg = Some(v)
}
}
///|
fn parse_args(argv : Array[String]) -> Args raise UsageError {
let a = Args::new()
let mut i = 0
let mut only_positional = false
while i < argv.length() {
let arg = argv[i]
i += 1
if only_positional || !arg.has_prefix("-") || arg == "-" {
if a.input is Some(_) {
raise UsageError("unrecognized arguments: \{arg}")
}
a.input = Some(arg)
continue
}
match arg {
"--" => {
only_positional = true
continue
}
"--help" => {
a.help = true
continue
}
"--json" => {
a.json = true
continue
}
_ => ()
}
if arg.has_prefix("--") {
raise UsageError("unrecognized arguments: \{arg}")
}
let chars = arg.to_array()
let mut k = 1
while k < chars.length() {
let c = chars[k]
k += 1
if takes_value(c) {
let v = if k < chars.length() {
String::from_array(chars[k:])
} else if i < argv.length() {
i += 1
argv[i - 1]
} else {
raise UsageError("argument -\{c}: expected one argument")
}
a.set_value(c, v)
break
}
match c {
'g' => a.guess = true
'v' => a.verbose = true
's' => a.stream = true
'x' => a.custom = true
'C' => a.guess_stdin = true
'V' => a.version = true
'h' => a.help = true
'L' => {
let what = []
if k < chars.length() {
what.push(String::from_array(chars[k:]))
k = chars.length()
}
while i < argv.length() && !argv[i].has_prefix("-") {
what.push(argv[i])
i += 1
}
a.list = Some(what)
}
'H' => {
if i + 1 >= argv.length() {
raise UsageError("argument -H: expected 2 arguments")
}
a.help_on = Some((argv[i], argv[i + 1]))
i += 2
}
_ => raise UsageError("unrecognized arguments: \{arg}")
}
}
}
if a.lexer is Some(_) && a.guess {
raise UsageError("argument -g: not allowed with argument -l")
}
let special = [
a.style is Some(_),
a.list is Some(_),
a.name_for_file is Some(_),
a.guess_stdin,
a.help_on is Some(_),
a.version,
a.help,
]
if special.filter(x => x).length() > 1 {
raise UsageError("special modes are mutually exclusive")
}
a
}
///|
/// Whether any option besides the special mode itself was given (Python's
/// `is_only_option`).
fn Args::has_other_than(self : Args, mode : String) -> Bool {
let checks = [
("l", self.lexer is Some(_)),
("g", self.guess),
("F", !self.filters.is_empty()),
("f", self.formatter is Some(_)),
("O", !self.options.is_empty()),
("P", !self.single_options.is_empty()),
("o", self.output is Some(_)),
("INPUTFILE", self.input is Some(_)),
("v", self.verbose),
("s", self.stream),
("x", self.custom),
("json", self.json),
("S", self.style is Some(_)),
("N", self.name_for_file is Some(_)),
("C", self.guess_stdin),
("H", self.help_on is Some(_)),
("V", self.version),
("h", self.help),
("a", self.style_arg is Some(_)),
]
checks.iter().any(c => c.0 != mode && c.1)
}
///|
let usage : String =
#|usage: pygmentize [-l LEXER | -g] [-F FILTER[:options]] [-f FORMATTER]
#| [-O OPTION=value[,OPTION=value,...]] [-P OPTION=value]
#| [-o OUTPUTFILE] [-v] [-s] [-x] [--json]
#| [-S STYLE -f formatter | -L [WHAT ...] | -N FILENAME | -C |
#| -H NAME TYPE | -V | -h] [-a ARG]
#| [INPUTFILE]
#|
///|
let help_text : String =
#|
#|Highlight an input file and write the result to an output file.
#|
#|Main operation:
#| -l LEXER Specify the lexer to use. (Query names with -L.) If not
#| given and -g is not present, the lexer is guessed from the
#| filename.
#| -g Guess the lexer from the file contents, or pass through as
#| plain text if nothing can be guessed.
#| -F FILTER[:options]
#| Add a filter to the token stream. (Query names with -L.)
#| Filter options are given after a colon if necessary.
#| -f FORMATTER Specify the formatter to use. (Query names with -L.) If not
#| given, the formatter is guessed from the output filename,
#| and defaults to the terminal formatter if the output is to
#| the terminal or an unknown file extension.
#| -O OPTION=value[,OPTION=value,...]
#| Give options to the lexer and formatter as a comma-
#| separated list of key-value pairs. Example: `-O
#| bg=light,python=cool`.
#| -P OPTION=value Give a single option to the lexer and formatter - with
#| this you can pass options whose value contains commas and
#| equal signs. Example: `-P "heading=Pygments, the Python
#| highlighter"`.
#| -o OUTPUTFILE Where to write the output. Defaults to standard output.
#| INPUTFILE Where to read the input. Defaults to standard input.
#|
#|Operation flags:
#| -v Print a detailed traceback on unhandled exceptions, which
#| is useful for debugging and bug reports.
#| -s Process lines one at a time until EOF, rather than waiting
#| to process the entire file. This only works for stdin,
#| only for lexers with no line-spanning constructs, and is
#| intended for streaming input such as you get from `tail
#| -f`. Example usage: `tail -f sql.log | pygmentize -s -l
#| sql`.
#| -x Allow custom lexers and formatters to be loaded from a .py
#| file (not supported by the MoonBit port).
#| --json Output as JSON. This can be only used in conjunction with
#| -L.
#|
#|Special modes - do not do any highlighting:
#| -S STYLE -f formatter
#| Print style definitions for STYLE for a formatter given
#| with -f. The argument given by -a is formatter dependent.
#| -L [WHAT ...] List lexers, formatters, styles or filters -- give
#| additional arguments for the thing(s) you want to list
#| (e.g. "styles"), or omit them to list everything.
#| -N FILENAME Guess and print out a lexer name based solely on the given
#| filename. Does not take input or highlight anything. If no
#| specific lexer can be determined, "text" is printed.
#| -C Like -N, but print out a lexer name based solely on a given
#| content from standard input.
#| -H NAME TYPE Print detailed help for the object of type ,
#| where is one of "lexer", "formatter" or "filter".
#| -V Print the package version.
#| -h, --help Print this help.
#| -a ARG Formatter-specific additional argument for the -S (print
#| style sheet) mode.
#|
///|
/// Python's `_parse_options`: comma-separated `key=value` pairs; a bare key
/// means `True`.
fn parse_options(o_strs : Array[String], into : Map[String, String]) -> Unit {
for o_str in o_strs {
if o_str.trim().length() == 0 {
continue
}
for o_arg in o_str.split(",") {
let o_arg = o_arg.trim().to_owned()
match o_arg.find("=") {
Some(i) =>
into[o_arg.unsafe_substring(start=0, end=i).trim().to_owned()] = o_arg
.unsafe_substring(start=i + 1, end=o_arg.length())
.trim()
.to_owned()
None => into[o_arg] = "True"
}
}
}
}
///|
/// Python's `_parse_filters`: `name[:options]`.
fn parse_filters(
f_strs : Array[String],
) -> Array[(String, Map[String, String])] {
f_strs.map(f => {
match f.find(":") {
Some(i) => {
let opts : Map[String, String] = Map([])
parse_options([f.unsafe_substring(start=i + 1, end=f.length())], opts)
(f.unsafe_substring(start=0, end=i), opts)
}
None => (f, Map([]))
}
})
}