5 Command line interface.
7 :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS.
8 :license: BSD, see LICENSE for details.
15 from textwrap
import dedent
17 from pip
._vendor
.pygments
import __version__
, highlight
18 from pip
._vendor
.pygments
.util
import ClassNotFound
, OptionError
, docstring_headline
, \
19 guess_decode
, guess_decode_from_terminal
, terminal_encoding
, \
20 UnclosingTextIOWrapper
21 from pip
._vendor
.pygments
.lexers
import get_all_lexers
, get_lexer_by_name
, guess_lexer
, \
22 load_lexer_from_file
, get_lexer_for_filename
, find_lexer_class_for_filename
23 from pip
._vendor
.pygments
.lexers
.special
import TextLexer
24 from pip
._vendor
.pygments
.formatters
.latex
import LatexEmbeddedLexer
, LatexFormatter
25 from pip
._vendor
.pygments
.formatters
import get_all_formatters
, get_formatter_by_name
, \
26 load_formatter_from_file
, get_formatter_for_filename
, find_formatter_class
27 from pip
._vendor
.pygments
.formatters
.terminal
import TerminalFormatter
28 from pip
._vendor
.pygments
.formatters
.terminal256
import Terminal256Formatter
, TerminalTrueColorFormatter
29 from pip
._vendor
.pygments
.filters
import get_all_filters
, find_filter_class
30 from pip
._vendor
.pygments
.styles
import get_all_styles
, get_style_by_name
33 def _parse_options(o_strs
):
40 o_args
= o_str
.split(',')
44 o_key
, o_val
= o_arg
.split('=', 1)
54 def _parse_filters(f_strs
):
60 fname
, fopts
= f_str
.split(':', 1)
61 filters
.append((fname
, _parse_options([fopts
])))
63 filters
.append((f_str
, {}))
67 def _print_help(what
, name
):
70 cls
= get_lexer_by_name(name
)
71 print("Help on the %s lexer:" % cls
.name
)
72 print(dedent(cls
.__doc
__))
73 elif what
== 'formatter':
74 cls
= find_formatter_class(name
)
75 print("Help on the %s formatter:" % cls
.name
)
76 print(dedent(cls
.__doc
__))
77 elif what
== 'filter':
78 cls
= find_filter_class(name
)
79 print("Help on the %s filter:" % name
)
80 print(dedent(cls
.__doc
__))
82 except (AttributeError, ValueError):
83 print("%s not found!" % what
, file=sys
.stderr
)
87 def _print_list(what
):
94 for fullname
, names
, exts
, _
in get_all_lexers():
95 tup
= (', '.join(names
)+':', fullname
,
96 exts
and '(filenames ' + ', '.join(exts
) + ')' or '')
100 print(('* %s\n %s %s') % i
)
102 elif what
== 'formatter':
108 for cls
in get_all_formatters():
109 doc
= docstring_headline(cls
)
110 tup
= (', '.join(cls
.aliases
) + ':', doc
, cls
.filenames
and
111 '(filenames ' + ', '.join(cls
.filenames
) + ')' or '')
115 print(('* %s\n %s %s') % i
)
117 elif what
== 'filter':
122 for name
in get_all_filters():
123 cls
= find_filter_class(name
)
124 print("* " + name
+ ':')
125 print(" %s" % docstring_headline(cls
))
127 elif what
== 'style':
132 for name
in get_all_styles():
133 cls
= get_style_by_name(name
)
134 print("* " + name
+ ':')
135 print(" %s" % docstring_headline(cls
))
138 def _print_list_as_json(requested_items
):
141 if 'lexer' in requested_items
:
143 for fullname
, names
, filenames
, mimetypes
in get_all_lexers():
146 'filenames': filenames
,
147 'mimetypes': mimetypes
149 result
['lexers'] = info
151 if 'formatter' in requested_items
:
153 for cls
in get_all_formatters():
154 doc
= docstring_headline(cls
)
156 'aliases': cls
.aliases
,
157 'filenames': cls
.filenames
,
160 result
['formatters'] = info
162 if 'filter' in requested_items
:
164 for name
in get_all_filters():
165 cls
= find_filter_class(name
)
167 'doc': docstring_headline(cls
)
169 result
['filters'] = info
171 if 'style' in requested_items
:
173 for name
in get_all_styles():
174 cls
= get_style_by_name(name
)
176 'doc': docstring_headline(cls
)
178 result
['styles'] = info
180 json
.dump(result
, sys
.stdout
)
182 def main_inner(parser
, argns
):
188 print('Pygments version %s, (c) 2006-2023 by Georg Brandl, Matthäus '
189 'Chajdas and contributors.' % __version__
)
192 def is_only_option(opt
):
193 return not any(v
for (k
, v
) in vars(argns
).items() if k
!= opt
)
195 # handle ``pygmentize -L``
196 if argns
.L
is not None:
198 for k
, v
in vars(argns
).items():
203 arg_set
.discard('json')
206 parser
.print_help(sys
.stderr
)
212 allowed_types
= {'lexer', 'formatter', 'filter', 'style'}
213 largs
= [arg
.rstrip('s') for arg
in argns
.L
]
214 if any(arg
not in allowed_types
for arg
in largs
):
215 parser
.print_help(sys
.stderr
)
218 largs
= allowed_types
223 _print_list_as_json(largs
)
226 # handle ``pygmentize -H``
228 if not is_only_option('H'):
229 parser
.print_help(sys
.stderr
)
232 if what
not in ('lexer', 'formatter', 'filter'):
233 parser
.print_help(sys
.stderr
)
235 return _print_help(what
, name
)
238 parsed_opts
= _parse_options(argns
.O
or [])
241 for p_opt
in argns
.P
or []:
243 name
, value
= p_opt
.split('=', 1)
245 parsed_opts
[p_opt
] = True
247 parsed_opts
[name
] = value
250 inencoding
= parsed_opts
.get('inencoding', parsed_opts
.get('encoding'))
251 outencoding
= parsed_opts
.get('outencoding', parsed_opts
.get('encoding'))
253 # handle ``pygmentize -N``
255 lexer
= find_lexer_class_for_filename(argns
.N
)
259 print(lexer
.aliases
[0])
262 # handle ``pygmentize -C``
264 inp
= sys
.stdin
.buffer.read()
266 lexer
= guess_lexer(inp
, inencoding
=inencoding
)
267 except ClassNotFound
:
270 print(lexer
.aliases
[0])
273 # handle ``pygmentize -S``
276 if S_opt
is not None:
279 parser
.print_help(sys
.stderr
)
281 if argns
.l
or argns
.INPUTFILE
:
282 parser
.print_help(sys
.stderr
)
286 parsed_opts
['style'] = S_opt
287 fmter
= get_formatter_by_name(f_opt
, **parsed_opts
)
288 except ClassNotFound
as err
:
289 print(err
, file=sys
.stderr
)
292 print(fmter
.get_style_defs(a_opt
or ''))
295 # if no -S is given, -a is not allowed
296 if argns
.a
is not None:
297 parser
.print_help(sys
.stderr
)
301 F_opts
= _parse_filters(argns
.F
or [])
303 # -x: allow custom (eXternal) lexers and formatters
304 allow_custom_lexer_formatter
= bool(argns
.x
)
312 # custom lexer, located relative to user's cwd
313 if allow_custom_lexer_formatter
and '.py' in lexername
:
318 filename
, name
= lexername
.rsplit(':', 1)
321 # This can happen on Windows: If the lexername is
322 # C:\lexer.py -- return to normal load path in that case
325 if filename
and name
:
326 lexer
= load_lexer_from_file(filename
, name
,
329 lexer
= load_lexer_from_file(lexername
, **parsed_opts
)
330 except ClassNotFound
as err
:
331 print('Error:', err
, file=sys
.stderr
)
335 lexer
= get_lexer_by_name(lexername
, **parsed_opts
)
336 except (OptionError
, ClassNotFound
) as err
:
337 print('Error:', err
, file=sys
.stderr
)
345 print('Error: -s option not usable when input file specified',
349 infn
= argns
.INPUTFILE
351 with open(infn
, 'rb') as infp
:
353 except Exception as err
:
354 print('Error: cannot read infile:', err
, file=sys
.stderr
)
357 code
, inencoding
= guess_decode(code
)
359 # do we have to guess the lexer?
362 lexer
= get_lexer_for_filename(infn
, code
, **parsed_opts
)
363 except ClassNotFound
as err
:
366 lexer
= guess_lexer(code
, **parsed_opts
)
367 except ClassNotFound
:
368 lexer
= TextLexer(**parsed_opts
)
370 print('Error:', err
, file=sys
.stderr
)
372 except OptionError
as err
:
373 print('Error:', err
, file=sys
.stderr
)
376 elif not argns
.s
: # treat stdin as full file (-s support is later)
377 # read code from terminal, always in binary mode since we want to
378 # decode ourselves and be tolerant with it
379 code
= sys
.stdin
.buffer.read() # use .buffer to get a binary stream
381 code
, inencoding
= guess_decode_from_terminal(code
, sys
.stdin
)
382 # else the lexer will do the decoding
385 lexer
= guess_lexer(code
, **parsed_opts
)
386 except ClassNotFound
:
387 lexer
= TextLexer(**parsed_opts
)
389 else: # -s option needs a lexer with -l
391 print('Error: when using -s a lexer has to be selected with -l',
396 for fname
, fopts
in F_opts
:
398 lexer
.add_filter(fname
, **fopts
)
399 except ClassNotFound
as err
:
400 print('Error:', err
, file=sys
.stderr
)
407 # custom formatter, located relative to user's cwd
408 if allow_custom_lexer_formatter
and '.py' in fmter
:
413 # Same logic as above for custom lexer
414 filename
, name
= fmter
.rsplit(':', 1)
419 if filename
and name
:
420 fmter
= load_formatter_from_file(filename
, name
,
423 fmter
= load_formatter_from_file(fmter
, **parsed_opts
)
424 except ClassNotFound
as err
:
425 print('Error:', err
, file=sys
.stderr
)
429 fmter
= get_formatter_by_name(fmter
, **parsed_opts
)
430 except (OptionError
, ClassNotFound
) as err
:
431 print('Error:', err
, file=sys
.stderr
)
437 fmter
= get_formatter_for_filename(outfn
, **parsed_opts
)
438 except (OptionError
, ClassNotFound
) as err
:
439 print('Error:', err
, file=sys
.stderr
)
442 outfile
= open(outfn
, 'wb')
443 except Exception as err
:
444 print('Error: cannot open outfile:', err
, file=sys
.stderr
)
448 if os
.environ
.get('COLORTERM','') in ('truecolor', '24bit'):
449 fmter
= TerminalTrueColorFormatter(**parsed_opts
)
450 elif '256' in os
.environ
.get('TERM', ''):
451 fmter
= Terminal256Formatter(**parsed_opts
)
453 fmter
= TerminalFormatter(**parsed_opts
)
454 outfile
= sys
.stdout
.buffer
456 # determine output encoding if not explicitly selected
459 # output file? use lexer encoding for now (can still be None)
460 fmter
.encoding
= inencoding
462 # else use terminal encoding
463 fmter
.encoding
= terminal_encoding(sys
.stdout
)
465 # provide coloring under Windows, if possible
466 if not outfn
and sys
.platform
in ('win32', 'cygwin') and \
467 fmter
.name
in ('Terminal', 'Terminal256'): # pragma: no cover
468 # unfortunately colorama doesn't support binary streams on Py3
469 outfile
= UnclosingTextIOWrapper(outfile
, encoding
=fmter
.encoding
)
470 fmter
.encoding
= None
472 import pip
._vendor
.colorama
.initialise
as colorama_initialise
476 outfile
= colorama_initialise
.wrap_stream(
477 outfile
, convert
=None, strip
=None, autoreset
=False, wrap
=True)
479 # When using the LaTeX formatter and the option `escapeinside` is
480 # specified, we need a special lexer which collects escaped text
481 # before running the chosen language lexer.
482 escapeinside
= parsed_opts
.get('escapeinside', '')
483 if len(escapeinside
) == 2 and isinstance(fmter
, LatexFormatter
):
484 left
= escapeinside
[0]
485 right
= escapeinside
[1]
486 lexer
= LatexEmbeddedLexer(left
, right
, lexer
)
490 # process whole input as per normal...
492 highlight(code
, lexer
, fmter
, outfile
)
498 # line by line processing of stdin (eg: for 'tail -f')...
501 line
= sys
.stdin
.buffer.readline()
505 line
= guess_decode_from_terminal(line
, sys
.stdin
)[0]
506 highlight(line
, lexer
, fmter
, outfile
)
507 if hasattr(outfile
, 'flush'):
510 except KeyboardInterrupt: # pragma: no cover
517 class HelpFormatter(argparse
.HelpFormatter
):
518 def __init__(self
, prog
, indent_increment
=2, max_help_position
=16, width
=None):
521 width
= shutil
.get_terminal_size().columns
- 2
524 argparse
.HelpFormatter
.__init
__(self
, prog
, indent_increment
,
525 max_help_position
, width
)
528 def main(args
=sys
.argv
):
530 Main command line entry point.
532 desc
= "Highlight an input file and write the result to an output file."
533 parser
= argparse
.ArgumentParser(description
=desc
, add_help
=False,
534 formatter_class
=HelpFormatter
)
536 operation
= parser
.add_argument_group('Main operation')
537 lexersel
= operation
.add_mutually_exclusive_group()
538 lexersel
.add_argument(
539 '-l', metavar
='LEXER',
540 help='Specify the lexer to use. (Query names with -L.) If not '
541 'given and -g is not present, the lexer is guessed from the filename.')
542 lexersel
.add_argument(
543 '-g', action
='store_true',
544 help='Guess the lexer from the file contents, or pass through '
545 'as plain text if nothing can be guessed.')
546 operation
.add_argument(
547 '-F', metavar
='FILTER[:options]', action
='append',
548 help='Add a filter to the token stream. (Query names with -L.) '
549 'Filter options are given after a colon if necessary.')
550 operation
.add_argument(
551 '-f', metavar
='FORMATTER',
552 help='Specify the formatter to use. (Query names with -L.) '
553 'If not given, the formatter is guessed from the output filename, '
554 'and defaults to the terminal formatter if the output is to the '
555 'terminal or an unknown file extension.')
556 operation
.add_argument(
557 '-O', metavar
='OPTION=value[,OPTION=value,...]', action
='append',
558 help='Give options to the lexer and formatter as a comma-separated '
559 'list of key-value pairs. '
560 'Example: `-O bg=light,python=cool`.')
561 operation
.add_argument(
562 '-P', metavar
='OPTION=value', action
='append',
563 help='Give a single option to the lexer and formatter - with this '
564 'you can pass options whose value contains commas and equal signs. '
565 'Example: `-P "heading=Pygments, the Python highlighter"`.')
566 operation
.add_argument(
567 '-o', metavar
='OUTPUTFILE',
568 help='Where to write the output. Defaults to standard output.')
570 operation
.add_argument(
571 'INPUTFILE', nargs
='?',
572 help='Where to read the input. Defaults to standard input.')
574 flags
= parser
.add_argument_group('Operation flags')
576 '-v', action
='store_true',
577 help='Print a detailed traceback on unhandled exceptions, which '
578 'is useful for debugging and bug reports.')
580 '-s', action
='store_true',
581 help='Process lines one at a time until EOF, rather than waiting to '
582 'process the entire file. This only works for stdin, only for lexers '
583 'with no line-spanning constructs, and is intended for streaming '
584 'input such as you get from `tail -f`. '
585 'Example usage: `tail -f sql.log | pygmentize -s -l sql`.')
587 '-x', action
='store_true',
588 help='Allow custom lexers and formatters to be loaded from a .py file '
589 'relative to the current working directory. For example, '
590 '`-l ./customlexer.py -x`. By default, this option expects a file '
591 'with a class named CustomLexer or CustomFormatter; you can also '
592 'specify your own class name with a colon (`-l ./lexer.py:MyLexer`). '
593 'Users should be very careful not to use this option with untrusted '
594 'files, because it will import and run them.')
595 flags
.add_argument('--json', help='Output as JSON. This can '
596 'be only used in conjunction with -L.',
600 special_modes_group
= parser
.add_argument_group(
601 'Special modes - do not do any highlighting')
602 special_modes
= special_modes_group
.add_mutually_exclusive_group()
603 special_modes
.add_argument(
604 '-S', metavar
='STYLE -f formatter',
605 help='Print style definitions for STYLE for a formatter '
606 'given with -f. The argument given by -a is formatter '
608 special_modes
.add_argument(
609 '-L', nargs
='*', metavar
='WHAT',
610 help='List lexers, formatters, styles or filters -- '
611 'give additional arguments for the thing(s) you want to list '
612 '(e.g. "styles"), or omit them to list everything.')
613 special_modes
.add_argument(
614 '-N', metavar
='FILENAME',
615 help='Guess and print out a lexer name based solely on the given '
616 'filename. Does not take input or highlight anything. If no specific '
617 'lexer can be determined, "text" is printed.')
618 special_modes
.add_argument(
619 '-C', action
='store_true',
620 help='Like -N, but print out a lexer name based solely on '
621 'a given content from standard input.')
622 special_modes
.add_argument(
623 '-H', action
='store', nargs
=2, metavar
=('NAME', 'TYPE'),
624 help='Print detailed help for the object <name> of type <type>, '
625 'where <type> is one of "lexer", "formatter" or "filter".')
626 special_modes
.add_argument(
627 '-V', action
='store_true',
628 help='Print the package version.')
629 special_modes
.add_argument(
630 '-h', '--help', action
='store_true',
631 help='Print this help.')
632 special_modes_group
.add_argument(
634 help='Formatter-specific additional argument for the -S (print '
635 'style sheet) mode.')
637 argns
= parser
.parse_args(args
[1:])
640 return main_inner(parser
, argns
)
641 except BrokenPipeError
:
642 # someone closed our stdout, e.g. by quitting a pager.
646 print(file=sys
.stderr
)
647 print('*' * 65, file=sys
.stderr
)
648 print('An unhandled exception occurred while highlighting.',
650 print('Please report the whole traceback to the issue tracker at',
652 print('<https://github.com/pygments/pygments/issues>.',
654 print('*' * 65, file=sys
.stderr
)
655 print(file=sys
.stderr
)
658 info
= traceback
.format_exception(*sys
.exc_info())
659 msg
= info
[-1].strip()
661 # extract relevant file and position info
662 msg
+= '\n (f%s)' % info
[-2].split('\n')[0].strip()[1:]
663 print(file=sys
.stderr
)
664 print('*** Error while highlighting:', file=sys
.stderr
)
665 print(msg
, file=sys
.stderr
)
666 print('*** If this is a bug you want to report, please rerun with -v.',