| | """ |
| | pygments.cmdline |
| | ~~~~~~~~~~~~~~~~ |
| | |
| | Command line interface. |
| | |
| | :copyright: Copyright 2006-2023 by the Pygments team, see AUTHORS. |
| | :license: BSD, see LICENSE for details. |
| | """ |
| |
|
| | import os |
| | import sys |
| | import shutil |
| | import argparse |
| | from textwrap import dedent |
| |
|
| | from pip._vendor.pygments import __version__, highlight |
| | from pip._vendor.pygments.util import ClassNotFound, OptionError, docstring_headline, \ |
| | guess_decode, guess_decode_from_terminal, terminal_encoding, \ |
| | UnclosingTextIOWrapper |
| | from pip._vendor.pygments.lexers import get_all_lexers, get_lexer_by_name, guess_lexer, \ |
| | load_lexer_from_file, get_lexer_for_filename, find_lexer_class_for_filename |
| | from pip._vendor.pygments.lexers.special import TextLexer |
| | from pip._vendor.pygments.formatters.latex import LatexEmbeddedLexer, LatexFormatter |
| | from pip._vendor.pygments.formatters import get_all_formatters, get_formatter_by_name, \ |
| | load_formatter_from_file, get_formatter_for_filename, find_formatter_class |
| | from pip._vendor.pygments.formatters.terminal import TerminalFormatter |
| | from pip._vendor.pygments.formatters.terminal256 import Terminal256Formatter, TerminalTrueColorFormatter |
| | from pip._vendor.pygments.filters import get_all_filters, find_filter_class |
| | from pip._vendor.pygments.styles import get_all_styles, get_style_by_name |
| |
|
| |
|
| | def _parse_options(o_strs): |
| | opts = {} |
| | if not o_strs: |
| | return opts |
| | for o_str in o_strs: |
| | if not o_str.strip(): |
| | continue |
| | o_args = o_str.split(',') |
| | for o_arg in o_args: |
| | o_arg = o_arg.strip() |
| | try: |
| | o_key, o_val = o_arg.split('=', 1) |
| | o_key = o_key.strip() |
| | o_val = o_val.strip() |
| | except ValueError: |
| | opts[o_arg] = True |
| | else: |
| | opts[o_key] = o_val |
| | return opts |
| |
|
| |
|
| | def _parse_filters(f_strs): |
| | filters = [] |
| | if not f_strs: |
| | return filters |
| | for f_str in f_strs: |
| | if ':' in f_str: |
| | fname, fopts = f_str.split(':', 1) |
| | filters.append((fname, _parse_options([fopts]))) |
| | else: |
| | filters.append((f_str, {})) |
| | return filters |
| |
|
| |
|
| | def _print_help(what, name): |
| | try: |
| | if what == 'lexer': |
| | cls = get_lexer_by_name(name) |
| | print("Help on the %s lexer:" % cls.name) |
| | print(dedent(cls.__doc__)) |
| | elif what == 'formatter': |
| | cls = find_formatter_class(name) |
| | print("Help on the %s formatter:" % cls.name) |
| | print(dedent(cls.__doc__)) |
| | elif what == 'filter': |
| | cls = find_filter_class(name) |
| | print("Help on the %s filter:" % name) |
| | print(dedent(cls.__doc__)) |
| | return 0 |
| | except (AttributeError, ValueError): |
| | print("%s not found!" % what, file=sys.stderr) |
| | return 1 |
| |
|
| |
|
| | def _print_list(what): |
| | if what == 'lexer': |
| | print() |
| | print("Lexers:") |
| | print("~~~~~~~") |
| |
|
| | info = [] |
| | for fullname, names, exts, _ in get_all_lexers(): |
| | tup = (', '.join(names)+':', fullname, |
| | exts and '(filenames ' + ', '.join(exts) + ')' or '') |
| | info.append(tup) |
| | info.sort() |
| | for i in info: |
| | print(('* %s\n %s %s') % i) |
| |
|
| | elif what == 'formatter': |
| | print() |
| | print("Formatters:") |
| | print("~~~~~~~~~~~") |
| |
|
| | info = [] |
| | for cls in get_all_formatters(): |
| | doc = docstring_headline(cls) |
| | tup = (', '.join(cls.aliases) + ':', doc, cls.filenames and |
| | '(filenames ' + ', '.join(cls.filenames) + ')' or '') |
| | info.append(tup) |
| | info.sort() |
| | for i in info: |
| | print(('* %s\n %s %s') % i) |
| |
|
| | elif what == 'filter': |
| | print() |
| | print("Filters:") |
| | print("~~~~~~~~") |
| |
|
| | for name in get_all_filters(): |
| | cls = find_filter_class(name) |
| | print("* " + name + ':') |
| | print(" %s" % docstring_headline(cls)) |
| |
|
| | elif what == 'style': |
| | print() |
| | print("Styles:") |
| | print("~~~~~~~") |
| |
|
| | for name in get_all_styles(): |
| | cls = get_style_by_name(name) |
| | print("* " + name + ':') |
| | print(" %s" % docstring_headline(cls)) |
| |
|
| |
|
| | def _print_list_as_json(requested_items): |
| | import json |
| | result = {} |
| | if 'lexer' in requested_items: |
| | info = {} |
| | for fullname, names, filenames, mimetypes in get_all_lexers(): |
| | info[fullname] = { |
| | 'aliases': names, |
| | 'filenames': filenames, |
| | 'mimetypes': mimetypes |
| | } |
| | result['lexers'] = info |
| |
|
| | if 'formatter' in requested_items: |
| | info = {} |
| | for cls in get_all_formatters(): |
| | doc = docstring_headline(cls) |
| | info[cls.name] = { |
| | 'aliases': cls.aliases, |
| | 'filenames': cls.filenames, |
| | 'doc': doc |
| | } |
| | result['formatters'] = info |
| |
|
| | if 'filter' in requested_items: |
| | info = {} |
| | for name in get_all_filters(): |
| | cls = find_filter_class(name) |
| | info[name] = { |
| | 'doc': docstring_headline(cls) |
| | } |
| | result['filters'] = info |
| |
|
| | if 'style' in requested_items: |
| | info = {} |
| | for name in get_all_styles(): |
| | cls = get_style_by_name(name) |
| | info[name] = { |
| | 'doc': docstring_headline(cls) |
| | } |
| | result['styles'] = info |
| |
|
| | json.dump(result, sys.stdout) |
| |
|
| | def main_inner(parser, argns): |
| | if argns.help: |
| | parser.print_help() |
| | return 0 |
| |
|
| | if argns.V: |
| | print('Pygments version %s, (c) 2006-2023 by Georg Brandl, Matthäus ' |
| | 'Chajdas and contributors.' % __version__) |
| | return 0 |
| |
|
| | def is_only_option(opt): |
| | return not any(v for (k, v) in vars(argns).items() if k != opt) |
| |
|
| | |
| | if argns.L is not None: |
| | arg_set = set() |
| | for k, v in vars(argns).items(): |
| | if v: |
| | arg_set.add(k) |
| |
|
| | arg_set.discard('L') |
| | arg_set.discard('json') |
| |
|
| | if arg_set: |
| | parser.print_help(sys.stderr) |
| | return 2 |
| |
|
| | |
| | if not argns.json: |
| | main(['', '-V']) |
| | allowed_types = {'lexer', 'formatter', 'filter', 'style'} |
| | largs = [arg.rstrip('s') for arg in argns.L] |
| | if any(arg not in allowed_types for arg in largs): |
| | parser.print_help(sys.stderr) |
| | return 0 |
| | if not largs: |
| | largs = allowed_types |
| | if not argns.json: |
| | for arg in largs: |
| | _print_list(arg) |
| | else: |
| | _print_list_as_json(largs) |
| | return 0 |
| |
|
| | |
| | if argns.H: |
| | if not is_only_option('H'): |
| | parser.print_help(sys.stderr) |
| | return 2 |
| | what, name = argns.H |
| | if what not in ('lexer', 'formatter', 'filter'): |
| | parser.print_help(sys.stderr) |
| | return 2 |
| | return _print_help(what, name) |
| |
|
| | |
| | parsed_opts = _parse_options(argns.O or []) |
| |
|
| | |
| | for p_opt in argns.P or []: |
| | try: |
| | name, value = p_opt.split('=', 1) |
| | except ValueError: |
| | parsed_opts[p_opt] = True |
| | else: |
| | parsed_opts[name] = value |
| |
|
| | |
| | inencoding = parsed_opts.get('inencoding', parsed_opts.get('encoding')) |
| | outencoding = parsed_opts.get('outencoding', parsed_opts.get('encoding')) |
| |
|
| | |
| | if argns.N: |
| | lexer = find_lexer_class_for_filename(argns.N) |
| | if lexer is None: |
| | lexer = TextLexer |
| |
|
| | print(lexer.aliases[0]) |
| | return 0 |
| |
|
| | |
| | if argns.C: |
| | inp = sys.stdin.buffer.read() |
| | try: |
| | lexer = guess_lexer(inp, inencoding=inencoding) |
| | except ClassNotFound: |
| | lexer = TextLexer |
| |
|
| | print(lexer.aliases[0]) |
| | return 0 |
| |
|
| | |
| | S_opt = argns.S |
| | a_opt = argns.a |
| | if S_opt is not None: |
| | f_opt = argns.f |
| | if not f_opt: |
| | parser.print_help(sys.stderr) |
| | return 2 |
| | if argns.l or argns.INPUTFILE: |
| | parser.print_help(sys.stderr) |
| | return 2 |
| |
|
| | try: |
| | parsed_opts['style'] = S_opt |
| | fmter = get_formatter_by_name(f_opt, **parsed_opts) |
| | except ClassNotFound as err: |
| | print(err, file=sys.stderr) |
| | return 1 |
| |
|
| | print(fmter.get_style_defs(a_opt or '')) |
| | return 0 |
| |
|
| | |
| | if argns.a is not None: |
| | parser.print_help(sys.stderr) |
| | return 2 |
| |
|
| | |
| | F_opts = _parse_filters(argns.F or []) |
| |
|
| | |
| | allow_custom_lexer_formatter = bool(argns.x) |
| |
|
| | |
| | lexer = None |
| |
|
| | |
| | lexername = argns.l |
| | if lexername: |
| | |
| | if allow_custom_lexer_formatter and '.py' in lexername: |
| | try: |
| | filename = None |
| | name = None |
| | if ':' in lexername: |
| | filename, name = lexername.rsplit(':', 1) |
| |
|
| | if '.py' in name: |
| | |
| | |
| | name = None |
| |
|
| | if filename and name: |
| | lexer = load_lexer_from_file(filename, name, |
| | **parsed_opts) |
| | else: |
| | lexer = load_lexer_from_file(lexername, **parsed_opts) |
| | except ClassNotFound as err: |
| | print('Error:', err, file=sys.stderr) |
| | return 1 |
| | else: |
| | try: |
| | lexer = get_lexer_by_name(lexername, **parsed_opts) |
| | except (OptionError, ClassNotFound) as err: |
| | print('Error:', err, file=sys.stderr) |
| | return 1 |
| |
|
| | |
| | code = None |
| |
|
| | if argns.INPUTFILE: |
| | if argns.s: |
| | print('Error: -s option not usable when input file specified', |
| | file=sys.stderr) |
| | return 2 |
| |
|
| | infn = argns.INPUTFILE |
| | try: |
| | with open(infn, 'rb') as infp: |
| | code = infp.read() |
| | except Exception as err: |
| | print('Error: cannot read infile:', err, file=sys.stderr) |
| | return 1 |
| | if not inencoding: |
| | code, inencoding = guess_decode(code) |
| |
|
| | |
| | if not lexer: |
| | try: |
| | lexer = get_lexer_for_filename(infn, code, **parsed_opts) |
| | except ClassNotFound as err: |
| | if argns.g: |
| | try: |
| | lexer = guess_lexer(code, **parsed_opts) |
| | except ClassNotFound: |
| | lexer = TextLexer(**parsed_opts) |
| | else: |
| | print('Error:', err, file=sys.stderr) |
| | return 1 |
| | except OptionError as err: |
| | print('Error:', err, file=sys.stderr) |
| | return 1 |
| |
|
| | elif not argns.s: |
| | |
| | |
| | code = sys.stdin.buffer.read() |
| | if not inencoding: |
| | code, inencoding = guess_decode_from_terminal(code, sys.stdin) |
| | |
| | if not lexer: |
| | try: |
| | lexer = guess_lexer(code, **parsed_opts) |
| | except ClassNotFound: |
| | lexer = TextLexer(**parsed_opts) |
| |
|
| | else: |
| | if not lexer: |
| | print('Error: when using -s a lexer has to be selected with -l', |
| | file=sys.stderr) |
| | return 2 |
| |
|
| | |
| | for fname, fopts in F_opts: |
| | try: |
| | lexer.add_filter(fname, **fopts) |
| | except ClassNotFound as err: |
| | print('Error:', err, file=sys.stderr) |
| | return 1 |
| |
|
| | |
| | outfn = argns.o |
| | fmter = argns.f |
| | if fmter: |
| | |
| | if allow_custom_lexer_formatter and '.py' in fmter: |
| | try: |
| | filename = None |
| | name = None |
| | if ':' in fmter: |
| | |
| | filename, name = fmter.rsplit(':', 1) |
| |
|
| | if '.py' in name: |
| | name = None |
| |
|
| | if filename and name: |
| | fmter = load_formatter_from_file(filename, name, |
| | **parsed_opts) |
| | else: |
| | fmter = load_formatter_from_file(fmter, **parsed_opts) |
| | except ClassNotFound as err: |
| | print('Error:', err, file=sys.stderr) |
| | return 1 |
| | else: |
| | try: |
| | fmter = get_formatter_by_name(fmter, **parsed_opts) |
| | except (OptionError, ClassNotFound) as err: |
| | print('Error:', err, file=sys.stderr) |
| | return 1 |
| |
|
| | if outfn: |
| | if not fmter: |
| | try: |
| | fmter = get_formatter_for_filename(outfn, **parsed_opts) |
| | except (OptionError, ClassNotFound) as err: |
| | print('Error:', err, file=sys.stderr) |
| | return 1 |
| | try: |
| | outfile = open(outfn, 'wb') |
| | except Exception as err: |
| | print('Error: cannot open outfile:', err, file=sys.stderr) |
| | return 1 |
| | else: |
| | if not fmter: |
| | if os.environ.get('COLORTERM','') in ('truecolor', '24bit'): |
| | fmter = TerminalTrueColorFormatter(**parsed_opts) |
| | elif '256' in os.environ.get('TERM', ''): |
| | fmter = Terminal256Formatter(**parsed_opts) |
| | else: |
| | fmter = TerminalFormatter(**parsed_opts) |
| | outfile = sys.stdout.buffer |
| |
|
| | |
| | if not outencoding: |
| | if outfn: |
| | |
| | fmter.encoding = inencoding |
| | else: |
| | |
| | fmter.encoding = terminal_encoding(sys.stdout) |
| |
|
| | |
| | if not outfn and sys.platform in ('win32', 'cygwin') and \ |
| | fmter.name in ('Terminal', 'Terminal256'): |
| | |
| | outfile = UnclosingTextIOWrapper(outfile, encoding=fmter.encoding) |
| | fmter.encoding = None |
| | try: |
| | import pip._vendor.colorama.initialise as colorama_initialise |
| | except ImportError: |
| | pass |
| | else: |
| | outfile = colorama_initialise.wrap_stream( |
| | outfile, convert=None, strip=None, autoreset=False, wrap=True) |
| |
|
| | |
| | |
| | |
| | escapeinside = parsed_opts.get('escapeinside', '') |
| | if len(escapeinside) == 2 and isinstance(fmter, LatexFormatter): |
| | left = escapeinside[0] |
| | right = escapeinside[1] |
| | lexer = LatexEmbeddedLexer(left, right, lexer) |
| |
|
| | |
| | if not argns.s: |
| | |
| | try: |
| | highlight(code, lexer, fmter, outfile) |
| | finally: |
| | if outfn: |
| | outfile.close() |
| | return 0 |
| | else: |
| | |
| | try: |
| | while 1: |
| | line = sys.stdin.buffer.readline() |
| | if not line: |
| | break |
| | if not inencoding: |
| | line = guess_decode_from_terminal(line, sys.stdin)[0] |
| | highlight(line, lexer, fmter, outfile) |
| | if hasattr(outfile, 'flush'): |
| | outfile.flush() |
| | return 0 |
| | except KeyboardInterrupt: |
| | return 0 |
| | finally: |
| | if outfn: |
| | outfile.close() |
| |
|
| |
|
| | class HelpFormatter(argparse.HelpFormatter): |
| | def __init__(self, prog, indent_increment=2, max_help_position=16, width=None): |
| | if width is None: |
| | try: |
| | width = shutil.get_terminal_size().columns - 2 |
| | except Exception: |
| | pass |
| | argparse.HelpFormatter.__init__(self, prog, indent_increment, |
| | max_help_position, width) |
| |
|
| |
|
| | def main(args=sys.argv): |
| | """ |
| | Main command line entry point. |
| | """ |
| | desc = "Highlight an input file and write the result to an output file." |
| | parser = argparse.ArgumentParser(description=desc, add_help=False, |
| | formatter_class=HelpFormatter) |
| |
|
| | operation = parser.add_argument_group('Main operation') |
| | lexersel = operation.add_mutually_exclusive_group() |
| | lexersel.add_argument( |
| | '-l', metavar='LEXER', |
| | help='Specify the lexer to use. (Query names with -L.) If not ' |
| | 'given and -g is not present, the lexer is guessed from the filename.') |
| | lexersel.add_argument( |
| | '-g', action='store_true', |
| | help='Guess the lexer from the file contents, or pass through ' |
| | 'as plain text if nothing can be guessed.') |
| | operation.add_argument( |
| | '-F', metavar='FILTER[:options]', action='append', |
| | help='Add a filter to the token stream. (Query names with -L.) ' |
| | 'Filter options are given after a colon if necessary.') |
| | operation.add_argument( |
| | '-f', metavar='FORMATTER', |
| | help='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.') |
| | operation.add_argument( |
| | '-O', metavar='OPTION=value[,OPTION=value,...]', action='append', |
| | help='Give options to the lexer and formatter as a comma-separated ' |
| | 'list of key-value pairs. ' |
| | 'Example: `-O bg=light,python=cool`.') |
| | operation.add_argument( |
| | '-P', metavar='OPTION=value', action='append', |
| | help='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"`.') |
| | operation.add_argument( |
| | '-o', metavar='OUTPUTFILE', |
| | help='Where to write the output. Defaults to standard output.') |
| |
|
| | operation.add_argument( |
| | 'INPUTFILE', nargs='?', |
| | help='Where to read the input. Defaults to standard input.') |
| |
|
| | flags = parser.add_argument_group('Operation flags') |
| | flags.add_argument( |
| | '-v', action='store_true', |
| | help='Print a detailed traceback on unhandled exceptions, which ' |
| | 'is useful for debugging and bug reports.') |
| | flags.add_argument( |
| | '-s', action='store_true', |
| | help='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`.') |
| | flags.add_argument( |
| | '-x', action='store_true', |
| | help='Allow custom lexers and formatters to be loaded from a .py file ' |
| | 'relative to the current working directory. For example, ' |
| | '`-l ./customlexer.py -x`. By default, this option expects a file ' |
| | 'with a class named CustomLexer or CustomFormatter; you can also ' |
| | 'specify your own class name with a colon (`-l ./lexer.py:MyLexer`). ' |
| | 'Users should be very careful not to use this option with untrusted ' |
| | 'files, because it will import and run them.') |
| | flags.add_argument('--json', help='Output as JSON. This can ' |
| | 'be only used in conjunction with -L.', |
| | default=False, |
| | action='store_true') |
| |
|
| | special_modes_group = parser.add_argument_group( |
| | 'Special modes - do not do any highlighting') |
| | special_modes = special_modes_group.add_mutually_exclusive_group() |
| | special_modes.add_argument( |
| | '-S', metavar='STYLE -f formatter', |
| | help='Print style definitions for STYLE for a formatter ' |
| | 'given with -f. The argument given by -a is formatter ' |
| | 'dependent.') |
| | special_modes.add_argument( |
| | '-L', nargs='*', metavar='WHAT', |
| | help='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.') |
| | special_modes.add_argument( |
| | '-N', metavar='FILENAME', |
| | help='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.') |
| | special_modes.add_argument( |
| | '-C', action='store_true', |
| | help='Like -N, but print out a lexer name based solely on ' |
| | 'a given content from standard input.') |
| | special_modes.add_argument( |
| | '-H', action='store', nargs=2, metavar=('NAME', 'TYPE'), |
| | help='Print detailed help for the object <name> of type <type>, ' |
| | 'where <type> is one of "lexer", "formatter" or "filter".') |
| | special_modes.add_argument( |
| | '-V', action='store_true', |
| | help='Print the package version.') |
| | special_modes.add_argument( |
| | '-h', '--help', action='store_true', |
| | help='Print this help.') |
| | special_modes_group.add_argument( |
| | '-a', metavar='ARG', |
| | help='Formatter-specific additional argument for the -S (print ' |
| | 'style sheet) mode.') |
| |
|
| | argns = parser.parse_args(args[1:]) |
| |
|
| | try: |
| | return main_inner(parser, argns) |
| | except BrokenPipeError: |
| | |
| | return 0 |
| | except Exception: |
| | if argns.v: |
| | print(file=sys.stderr) |
| | print('*' * 65, file=sys.stderr) |
| | print('An unhandled exception occurred while highlighting.', |
| | file=sys.stderr) |
| | print('Please report the whole traceback to the issue tracker at', |
| | file=sys.stderr) |
| | print('<https://github.com/pygments/pygments/issues>.', |
| | file=sys.stderr) |
| | print('*' * 65, file=sys.stderr) |
| | print(file=sys.stderr) |
| | raise |
| | import traceback |
| | info = traceback.format_exception(*sys.exc_info()) |
| | msg = info[-1].strip() |
| | if len(info) >= 3: |
| | |
| | msg += '\n (f%s)' % info[-2].split('\n')[0].strip()[1:] |
| | print(file=sys.stderr) |
| | print('*** Error while highlighting:', file=sys.stderr) |
| | print(msg, file=sys.stderr) |
| | print('*** If this is a bug you want to report, please rerun with -v.', |
| | file=sys.stderr) |
| | return 1 |
| |
|