diff --git a/tests/test_cli.py b/tests/test_cli.py index 29feb1a..b5ef82a 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -140,6 +140,17 @@ class CommandLineTestCase(unittest.TestCase): r'not allowed with argument -c\/--config-file$' ) + # checks if reading from stdin and files are mutually exclusive + sys.stdout, sys.stderr = StringIO(), StringIO() + with self.assertRaises(SystemExit) as ctx: + cli.run(('-', 'file')) + + self.assertNotEqual(ctx.exception.code, 0) + + out, err = sys.stdout.getvalue(), sys.stderr.getvalue() + self.assertEqual(out, '') + self.assertRegexpMatches(err, r'^usage') + def test_run_with_bad_config(self): sys.stdout, sys.stderr = StringIO(), StringIO() with self.assertRaises(SystemExit) as ctx: @@ -434,3 +445,22 @@ class CommandLineTestCase(unittest.TestCase): '\033[2m(new-line-at-end-of-file)\033[0m\n' '\n' % file)) self.assertEqual(err, '') + + def test_run_read_from_stdin(self): + # prepares stdin with an invalid yaml string so that we can check + # for its specific error, and be assured that stdin was read + sys.stdout, sys.stderr = StringIO(), StringIO() + sys.stdin = StringIO( + 'I am a string\n' + 'therefore: I am an error\n') + + with self.assertRaises(SystemExit) as ctx: + cli.run(('-', '-f', 'parsable')) + + self.assertNotEqual(ctx.exception.code, 0) + + out, err = sys.stdout.getvalue(), sys.stderr.getvalue() + self.assertEqual(out, ( + 'stdin:2:10: [error] syntax error: ' + 'mapping values are not allowed here\n')) + self.assertEqual(err, '') diff --git a/yamllint/cli.py b/yamllint/cli.py index 8529fb7..c5916e5 100644 --- a/yamllint/cli.py +++ b/yamllint/cli.py @@ -83,11 +83,41 @@ class Format(object): return line +def show_problems(problems, file, args_format): + max_level = 0 + first = True + + for problem in problems: + if args_format == 'parsable': + print(Format.parsable(problem, file)) + elif args_format == 'colored' or \ + (args_format == 'auto' and supports_color()): + 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)) + max_level = max(max_level, PROBLEM_LEVELS[problem.level]) + + if not first and args_format != 'parsable': + print('') + + return max_level + + def run(argv=None): parser = argparse.ArgumentParser(prog=APP_NAME, description=APP_DESCRIPTION) - parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+', - help='files to check') + files_group = parser.add_mutually_exclusive_group(required=True) + files_group.add_argument('files', metavar='FILE_OR_DIR', nargs='*', + default=(), + help='files to check') + files_group.add_argument('-', action='store_true', dest='stdin', + help='read from standard input') config_group = parser.add_mutually_exclusive_group() config_group.add_argument('-c', '--config-file', dest='config_file', action='store', @@ -105,8 +135,6 @@ def run(argv=None): parser.add_argument('-v', '--version', action='version', version='{} {}'.format(APP_NAME, APP_VERSION)) - # TODO: read from stdin when no filename? - args = parser.parse_args(argv) # User-global config is supposed to be in ~/.config/yamllint/config @@ -138,32 +166,23 @@ def run(argv=None): for file in find_files_recursively(args.files): filepath = file[2:] if file.startswith('./') else file try: - first = True with open(file) as f: - for problem in linter.run(f, conf, filepath): - if args.format == 'parsable': - print(Format.parsable(problem, file)) - elif args.format == 'colored' or \ - (args.format == 'auto' and supports_color()): - 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)) - - max_level = max(max_level, PROBLEM_LEVELS[problem.level]) - - if not first and args.format != 'parsable': - print('') + problems = linter.run(f, conf, filepath) + except EnvironmentError as e: + print(e, file=sys.stderr) + sys.exit(-1) + prob_level = show_problems(problems, file, args_format=args.format) + max_level = max(max_level, prob_level) + + # read yaml from stdin + if args.stdin: + try: + problems = linter.run(sys.stdin, conf, '') except EnvironmentError as e: print(e, file=sys.stderr) sys.exit(-1) + prob_level = show_problems(problems, 'stdin', args_format=args.format) + max_level = max(max_level, prob_level) if max_level == PROBLEM_LEVELS['error']: return_code = 1