feat(cli): Colour output only on TTY
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
This commit is contained in:
@@ -18,8 +18,10 @@ try:
|
|||||||
from cStringIO import StringIO
|
from cStringIO import StringIO
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
import fcntl
|
||||||
import locale
|
import locale
|
||||||
import os
|
import os
|
||||||
|
import pty
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
@@ -308,7 +310,7 @@ class CommandLineTestCase(unittest.TestCase):
|
|||||||
'(key-duplicates)\n') % file)
|
'(key-duplicates)\n') % file)
|
||||||
self.assertEqual(err, '')
|
self.assertEqual(err, '')
|
||||||
|
|
||||||
def test_run_colored_output(self):
|
def test_run_piped_output_nocolor(self):
|
||||||
file = os.path.join(self.wd, 'a.yaml')
|
file = os.path.join(self.wd, 'a.yaml')
|
||||||
|
|
||||||
sys.stdout, sys.stderr = StringIO(), StringIO()
|
sys.stdout, sys.stderr = StringIO(), StringIO()
|
||||||
@@ -318,6 +320,38 @@ class CommandLineTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(ctx.exception.code, 1)
|
self.assertEqual(ctx.exception.code, 1)
|
||||||
|
|
||||||
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
|
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, (
|
self.assertEqual(out, (
|
||||||
'\033[4m%s\033[0m\n'
|
'\033[4m%s\033[0m\n'
|
||||||
' \033[2m2:4\033[0m \033[31merror\033[0m '
|
' \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 '
|
'no new line character at the end of file '
|
||||||
'\033[2m(new-line-at-end-of-file)\033[0m\n'
|
'\033[2m(new-line-at-end-of-file)\033[0m\n'
|
||||||
'\n' % file))
|
'\n' % file))
|
||||||
self.assertEqual(err, '')
|
|
||||||
|
|||||||
@@ -48,6 +48,17 @@ class Format(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def standard(problem, filename):
|
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 = ' \033[2m%d:%d\033[0m' % (problem.line, problem.column)
|
||||||
line += max(20 - len(line), 0) * ' '
|
line += max(20 - len(line), 0) * ' '
|
||||||
if problem.level == 'warning':
|
if problem.level == 'warning':
|
||||||
@@ -119,11 +130,17 @@ def run(argv=None):
|
|||||||
for problem in linter.run(f, conf):
|
for problem in linter.run(f, conf):
|
||||||
if args.format == 'parsable':
|
if args.format == 'parsable':
|
||||||
print(Format.parsable(problem, file))
|
print(Format.parsable(problem, file))
|
||||||
else:
|
elif sys.stdout.isatty():
|
||||||
if first:
|
if first:
|
||||||
print('\033[4m%s\033[0m' % file)
|
print('\033[4m%s\033[0m' % file)
|
||||||
first = False
|
first = False
|
||||||
|
|
||||||
|
print(Format.standard_color(problem, file))
|
||||||
|
else:
|
||||||
|
if first:
|
||||||
|
print(file)
|
||||||
|
first = False
|
||||||
|
|
||||||
print(Format.standard(problem, file))
|
print(Format.standard(problem, file))
|
||||||
|
|
||||||
if return_code == 0 and problem.level == 'error':
|
if return_code == 0 and problem.level == 'error':
|
||||||
|
|||||||
Reference in New Issue
Block a user