1# -*- coding: utf-8 -*- 2""" 3 Command line test 4 ~~~~~~~~~~~~~~~~~ 5 6 :copyright: Copyright 2006-2020 by the Pygments team, see AUTHORS. 7 :license: BSD, see LICENSE for details. 8""" 9 10import io 11import os 12import re 13import sys 14import tempfile 15from io import BytesIO 16from os import path 17 18from pytest import raises 19 20from pygments import cmdline, highlight 21 22TESTDIR = path.dirname(path.abspath(__file__)) 23TESTFILE = path.join(TESTDIR, 'test_cmdline.py') 24 25TESTCODE = '''\ 26def func(args): 27 pass 28''' 29 30 31def _decode_output(text): 32 try: 33 return text.decode('utf-8') 34 except UnicodeEncodeError: # implicit encode on Python 2 with data loss 35 return text 36 37 38def run_cmdline(*args, **kwds): 39 saved_stdin = sys.stdin 40 saved_stdout = sys.stdout 41 saved_stderr = sys.stderr 42 stdin_buffer = BytesIO() 43 stdout_buffer = BytesIO() 44 stderr_buffer = BytesIO() 45 new_stdin = sys.stdin = io.TextIOWrapper(stdin_buffer, 'utf-8') 46 new_stdout = sys.stdout = io.TextIOWrapper(stdout_buffer, 'utf-8') 47 new_stderr = sys.stderr = io.TextIOWrapper(stderr_buffer, 'utf-8') 48 new_stdin.write(kwds.get('stdin', '')) 49 new_stdin.seek(0, 0) 50 try: 51 ret = cmdline.main(['pygmentize'] + list(args)) 52 finally: 53 sys.stdin = saved_stdin 54 sys.stdout = saved_stdout 55 sys.stderr = saved_stderr 56 new_stdout.flush() 57 new_stderr.flush() 58 out, err = stdout_buffer.getvalue(), \ 59 stderr_buffer.getvalue() 60 return (ret, _decode_output(out), _decode_output(err)) 61 62 63def check_success(*cmdline, **kwds): 64 code, out, err = run_cmdline(*cmdline, **kwds) 65 assert code == 0 66 assert err == '' 67 return out 68 69 70def check_failure(*cmdline, **kwds): 71 expected_code = kwds.pop('code', 1) 72 code, out, err = run_cmdline(*cmdline, **kwds) 73 assert code == expected_code 74 assert out == '' 75 return err 76 77 78def test_normal(): 79 # test that cmdline gives the same output as library api 80 from pygments.lexers import PythonLexer 81 from pygments.formatters import HtmlFormatter 82 filename = TESTFILE 83 with open(filename, 'rb') as fp: 84 code = fp.read() 85 86 output = highlight(code, PythonLexer(), HtmlFormatter()) 87 88 o = check_success('-lpython', '-fhtml', filename) 89 assert o == output 90 91 92def test_stdin(): 93 o = check_success('-lpython', '-fhtml', stdin=TESTCODE) 94 o = re.sub('<[^>]*>', '', o) 95 # rstrip is necessary since HTML inserts a \n after the last </div> 96 assert o.rstrip() == TESTCODE.rstrip() 97 98 # guess if no lexer given 99 o = check_success('-fhtml', stdin=TESTCODE) 100 o = re.sub('<[^>]*>', '', o) 101 # rstrip is necessary since HTML inserts a \n after the last </div> 102 assert o.rstrip() == TESTCODE.rstrip() 103 104 105def test_outfile(): 106 # test that output file works with and without encoding 107 fd, name = tempfile.mkstemp() 108 os.close(fd) 109 for opts in [['-fhtml', '-o', name, TESTFILE], 110 ['-flatex', '-o', name, TESTFILE], 111 ['-fhtml', '-o', name, '-O', 'encoding=utf-8', TESTFILE]]: 112 try: 113 check_success(*opts) 114 finally: 115 os.unlink(name) 116 117 118def test_load_from_file(): 119 lexer_file = os.path.join(TESTDIR, 'support', 'python_lexer.py') 120 formatter_file = os.path.join(TESTDIR, 'support', 'html_formatter.py') 121 122 # By default, use CustomLexer 123 o = check_success('-l', lexer_file, '-f', 'html', '-x', stdin=TESTCODE) 124 o = re.sub('<[^>]*>', '', o) 125 # rstrip is necessary since HTML inserts a \n after the last </div> 126 assert o.rstrip() == TESTCODE.rstrip() 127 128 # If user specifies a name, use it 129 o = check_success('-f', 'html', '-x', '-l', 130 lexer_file + ':LexerWrapper', stdin=TESTCODE) 131 o = re.sub('<[^>]*>', '', o) 132 # rstrip is necessary since HTML inserts a \n after the last </div> 133 assert o.rstrip() == TESTCODE.rstrip() 134 135 # Should also work for formatters 136 o = check_success('-lpython', '-f', 137 formatter_file + ':HtmlFormatterWrapper', 138 '-x', stdin=TESTCODE) 139 o = re.sub('<[^>]*>', '', o) 140 # rstrip is necessary since HTML inserts a \n after the last </div> 141 assert o.rstrip() == TESTCODE.rstrip() 142 143 144def test_stream_opt(): 145 o = check_success('-lpython', '-s', '-fterminal', stdin=TESTCODE) 146 o = re.sub(r'\x1b\[.*?m', '', o) 147 assert o.replace('\r\n', '\n') == TESTCODE 148 149 150def test_h_opt(): 151 o = check_success('-h') 152 assert 'Usage:' in o 153 154 155def test_L_opt(): 156 o = check_success('-L') 157 assert 'Lexers' in o and 'Formatters' in o and 'Filters' in o and 'Styles' in o 158 o = check_success('-L', 'lexer') 159 assert 'Lexers' in o and 'Formatters' not in o 160 check_success('-L', 'lexers') 161 162 163def test_O_opt(): 164 filename = TESTFILE 165 o = check_success('-Ofull=1,linenos=true,foo=bar', '-fhtml', filename) 166 assert '<html' in o 167 assert 'class="linenos"' in o 168 169 # "foobar" is invalid for a bool option 170 e = check_failure('-Ostripnl=foobar', TESTFILE) 171 assert 'Error: Invalid value' in e 172 e = check_failure('-Ostripnl=foobar', '-lpy') 173 assert 'Error: Invalid value' in e 174 175 176def test_P_opt(): 177 filename = TESTFILE 178 o = check_success('-Pfull', '-Ptitle=foo, bar=baz=,', '-fhtml', filename) 179 assert '<title>foo, bar=baz=,</title>' in o 180 181 182def test_F_opt(): 183 filename = TESTFILE 184 o = check_success('-Fhighlight:tokentype=Name.Blubb,' 185 'names=TESTFILE filename', '-fhtml', filename) 186 assert '<span class="n n-Blubb' in o 187 188 189def test_H_opt(): 190 o = check_success('-H', 'formatter', 'html') 191 assert 'HTML' in o 192 o = check_success('-H', 'lexer', 'python') 193 assert 'Python' in o 194 o = check_success('-H', 'filter', 'raiseonerror') 195 assert 'raiseonerror' in o 196 e = check_failure('-H', 'lexer', 'foobar') 197 assert 'not found' in e 198 199 200def test_S_opt(): 201 o = check_success('-S', 'default', '-f', 'html', '-O', 'linenos=1') 202 lines = o.splitlines() 203 for line in lines[5:]: 204 # every line is for a token class, except for the first 5 lines, 205 # which define styles for `pre` and line numbers 206 parts = line.split() 207 assert parts[0].startswith('.') 208 assert parts[1] == '{' 209 if parts[0] != '.hll': 210 assert parts[-4] == '}' 211 assert parts[-3] == '/*' 212 assert parts[-1] == '*/' 213 check_failure('-S', 'default', '-f', 'foobar') 214 215 216def test_N_opt(): 217 o = check_success('-N', 'test.py') 218 assert 'python' == o.strip() 219 o = check_success('-N', 'test.unknown') 220 assert 'text' == o.strip() 221 222 223def test_invalid_opts(): 224 for opts in [ 225 ('-X',), 226 ('-L', '-lpy'), 227 ('-L', '-fhtml'), 228 ('-L', '-Ox'), 229 ('-S', 'default', '-l', 'py', '-f', 'html'), 230 ('-S', 'default'), 231 ('-a', 'arg'), 232 ('-H',), 233 (TESTFILE, TESTFILE), 234 ('-H', 'formatter'), 235 ('-H', 'foo', 'bar'), 236 ('-s',), 237 ('-s', TESTFILE), 238 ]: 239 check_failure(*opts, code=2) 240 241 242def test_errors(): 243 # input file not found 244 e = check_failure('-lpython', 'nonexistent.py') 245 assert 'Error: cannot read infile' in e 246 assert 'nonexistent.py' in e 247 248 # lexer not found 249 e = check_failure('-lfooo', TESTFILE) 250 assert 'Error: no lexer for alias' in e 251 252 # cannot load .py file without load_from_file flag 253 e = check_failure('-l', 'nonexistent.py', TESTFILE) 254 assert 'Error: no lexer for alias' in e 255 256 # lexer file is missing/unreadable 257 e = check_failure('-l', 'nonexistent.py', '-x', TESTFILE) 258 assert 'Error: cannot read' in e 259 260 # lexer file is malformed 261 e = check_failure('-l', path.join(TESTDIR, 'support', 'empty.py'), 262 '-x', TESTFILE) 263 assert 'Error: no valid CustomLexer class found' in e 264 265 # formatter not found 266 e = check_failure('-lpython', '-ffoo', TESTFILE) 267 assert 'Error: no formatter found for name' in e 268 269 # formatter for outfile not found 270 e = check_failure('-ofoo.foo', TESTFILE) 271 assert 'Error: no formatter found for file name' in e 272 273 # cannot load .py file without load_from_file flag 274 e = check_failure('-f', 'nonexistent.py', TESTFILE) 275 assert 'Error: no formatter found for name' in e 276 277 # formatter file is missing/unreadable 278 e = check_failure('-f', 'nonexistent.py', '-x', TESTFILE) 279 assert 'Error: cannot read' in e 280 281 # formatter file is malformed 282 e = check_failure('-f', path.join(TESTDIR, 'support', 'empty.py'), 283 '-x', TESTFILE) 284 assert 'Error: no valid CustomFormatter class found' in e 285 286 # output file not writable 287 e = check_failure('-o', os.path.join('nonexistent', 'dir', 'out.html'), 288 '-lpython', TESTFILE) 289 assert 'Error: cannot open outfile' in e 290 assert 'out.html' in e 291 292 # unknown filter 293 e = check_failure('-F', 'foo', TESTFILE) 294 assert 'Error: filter \'foo\' not found' in e 295 296 297def test_exception(): 298 cmdline.highlight = None # override callable to provoke TypeError 299 try: 300 # unexpected exception while highlighting 301 e = check_failure('-lpython', TESTFILE) 302 assert '*** Error while highlighting:' in e 303 assert 'TypeError' in e 304 305 # same with -v: should reraise the exception 306 assert raises(Exception, check_failure, '-lpython', '-v', TESTFILE) 307 finally: 308 cmdline.highlight = highlight 309 310 311def test_parse_opts(): 312 assert cmdline._parse_options([' ', 'keyonly,key = value ']) == \ 313 {'keyonly': True, 'key': 'value'} 314