From a2c68fdf9bf2332631b9c25fa18b86ab5001b0d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Fri, 12 Aug 2016 10:51:59 +0200 Subject: [PATCH] feat(cli): Colour output only on TTY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When piping yamllint output to a file, "coloured" characters aren't interpreted and pollute text formatting with glyphs like: �[4m./global.yaml�[0m �[2m1439:52�[0m �[31merror�[0m no new line character... With this commit, stdout is checked: if it's a TTY then output is coloured, otherwise output is simple text. Closes: #14 --- tests/test_cli.py | 37 +++++++++++++++++++++++++++++++++++-- yamllint/cli.py | 19 ++++++++++++++++++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index ed4e76d..da139a8 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -18,8 +18,10 @@ try: from cStringIO import StringIO except ImportError: from io import StringIO +import fcntl import locale import os +import pty import shutil import tempfile import unittest @@ -308,7 +310,7 @@ class CommandLineTestCase(unittest.TestCase): '(key-duplicates)\n') % file) self.assertEqual(err, '') - def test_run_colored_output(self): + def test_run_piped_output_nocolor(self): file = os.path.join(self.wd, 'a.yaml') sys.stdout, sys.stderr = StringIO(), StringIO() @@ -318,6 +320,38 @@ class CommandLineTestCase(unittest.TestCase): self.assertEqual(ctx.exception.code, 1) out, err = sys.stdout.getvalue(), sys.stderr.getvalue() + self.assertEqual(out, ( + '%s\n' + ' 2:4 error trailing spaces (trailing-spaces)\n' + ' 3:4 error no new line character at the end of file ' + '(new-line-at-end-of-file)\n' + '\n' % file)) + self.assertEqual(err, '') + + def test_run_colored_output(self): + file = os.path.join(self.wd, 'a.yaml') + + # Create a pseudo-TTY and redirect stdout to it + master, slave = pty.openpty() + sys.stdout = sys.stderr = os.fdopen(slave, 'w') + + with self.assertRaises(SystemExit) as ctx: + cli.run((file, )) + sys.stdout.flush() + + self.assertEqual(ctx.exception.code, 1) + + # Read output from TTY + output = os.fdopen(master, 'r') + flag = fcntl.fcntl(master, fcntl.F_GETFD) + fcntl.fcntl(master, fcntl.F_SETFL, flag | os.O_NONBLOCK) + + out = output.read().replace('\r\n', '\n') + + sys.stdout.close() + sys.stderr.close() + output.close() + self.assertEqual(out, ( '\033[4m%s\033[0m\n' ' \033[2m2:4\033[0m \033[31merror\033[0m ' @@ -326,4 +360,3 @@ class CommandLineTestCase(unittest.TestCase): 'no new line character at the end of file ' '\033[2m(new-line-at-end-of-file)\033[0m\n' '\n' % file)) - self.assertEqual(err, '') diff --git a/yamllint/cli.py b/yamllint/cli.py index 525e162..9e3e5f3 100644 --- a/yamllint/cli.py +++ b/yamllint/cli.py @@ -48,6 +48,17 @@ class Format(object): @staticmethod def standard(problem, filename): + line = ' %d:%d' % (problem.line, problem.column) + line += max(12 - len(line), 0) * ' ' + line += problem.level + line += max(21 - len(line), 0) * ' ' + line += problem.desc + if problem.rule: + line += ' (%s)' % problem.rule + return line + + @staticmethod + def standard_color(problem, filename): line = ' \033[2m%d:%d\033[0m' % (problem.line, problem.column) line += max(20 - len(line), 0) * ' ' if problem.level == 'warning': @@ -119,11 +130,17 @@ def run(argv=None): for problem in linter.run(f, conf): if args.format == 'parsable': print(Format.parsable(problem, file)) - else: + elif sys.stdout.isatty(): if first: print('\033[4m%s\033[0m' % file) first = False + print(Format.standard_color(problem, file)) + else: + if first: + print(file) + first = False + print(Format.standard(problem, file)) if return_code == 0 and problem.level == 'error':