mirror of
https://github.com/thegeeklab/ansible-later.git
synced 2024-11-29 16:20:35 +00:00
195 lines
7.1 KiB
Python
195 lines
7.1 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
# Copyright (C) 2016 Adrien Vergé
|
||
|
#
|
||
|
# This program is free software: you can redistribute it and/or modify
|
||
|
# it under the terms of the GNU General Public License as published by
|
||
|
# the Free Software Foundation, either version 3 of the License, or
|
||
|
# (at your option) any later version.
|
||
|
#
|
||
|
# This program is distributed in the hope that it will be useful,
|
||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
# GNU General Public License for more details.
|
||
|
#
|
||
|
# You should have received a copy of the GNU General Public License
|
||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
from __future__ import print_function
|
||
|
|
||
|
import os
|
||
|
import sys
|
||
|
import platform
|
||
|
import argparse
|
||
|
|
||
|
from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION
|
||
|
from yamllint.config import YamlLintConfig, YamlLintConfigError
|
||
|
from yamllint.linter import PROBLEM_LEVELS
|
||
|
from yamllint import linter
|
||
|
|
||
|
|
||
|
def find_files_recursively(items):
|
||
|
for item in items:
|
||
|
if os.path.isdir(item):
|
||
|
for root, dirnames, filenames in os.walk(item):
|
||
|
for filename in [f for f in filenames
|
||
|
if f.endswith(('.yml', '.yaml'))]:
|
||
|
yield os.path.join(root, filename)
|
||
|
else:
|
||
|
yield item
|
||
|
|
||
|
|
||
|
def supports_color():
|
||
|
supported_platform = not (platform.system() == 'Windows' and not
|
||
|
('ANSICON' in os.environ or
|
||
|
('TERM' in os.environ and
|
||
|
os.environ['TERM'] == 'ANSI')))
|
||
|
return (supported_platform and
|
||
|
hasattr(sys.stdout, 'isatty') and sys.stdout.isatty())
|
||
|
|
||
|
|
||
|
class Format(object):
|
||
|
@staticmethod
|
||
|
def parsable(problem, filename):
|
||
|
return ('%(file)s:%(line)s:%(column)s: [%(level)s] %(message)s' %
|
||
|
{'file': filename,
|
||
|
'line': problem.line,
|
||
|
'column': problem.column,
|
||
|
'level': problem.level,
|
||
|
'message': problem.message})
|
||
|
|
||
|
@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':
|
||
|
line += '\033[33m%s\033[0m' % problem.level
|
||
|
else:
|
||
|
line += '\033[31m%s\033[0m' % problem.level
|
||
|
line += max(38 - len(line), 0) * ' '
|
||
|
line += problem.desc
|
||
|
if problem.rule:
|
||
|
line += ' \033[2m(%s)\033[0m' % problem.rule
|
||
|
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)
|
||
|
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',
|
||
|
help='path to a custom configuration')
|
||
|
config_group.add_argument('-d', '--config-data', dest='config_data',
|
||
|
action='store',
|
||
|
help='custom configuration (as YAML source)')
|
||
|
parser.add_argument('-f', '--format',
|
||
|
choices=('parsable', 'standard', 'colored', 'auto'),
|
||
|
default='auto', help='format for parsing output')
|
||
|
parser.add_argument('-s', '--strict',
|
||
|
action='store_true',
|
||
|
help='return non-zero exit code on warnings '
|
||
|
'as well as errors')
|
||
|
parser.add_argument('-v', '--version', action='version',
|
||
|
version='{} {}'.format(APP_NAME, APP_VERSION))
|
||
|
|
||
|
args = parser.parse_args(argv)
|
||
|
|
||
|
# User-global config is supposed to be in ~/.config/yamllint/config
|
||
|
if 'XDG_CONFIG_HOME' in os.environ:
|
||
|
user_global_config = os.path.join(
|
||
|
os.environ['XDG_CONFIG_HOME'], 'yamllint', 'config')
|
||
|
else:
|
||
|
user_global_config = os.path.expanduser('~/.config/yamllint/config')
|
||
|
|
||
|
try:
|
||
|
if args.config_data is not None:
|
||
|
if args.config_data != '' and ':' not in args.config_data:
|
||
|
args.config_data = 'extends: ' + args.config_data
|
||
|
conf = YamlLintConfig(content=args.config_data)
|
||
|
elif args.config_file is not None:
|
||
|
conf = YamlLintConfig(file=args.config_file)
|
||
|
elif os.path.isfile('.yamllint'):
|
||
|
conf = YamlLintConfig(file='.yamllint')
|
||
|
elif os.path.isfile(user_global_config):
|
||
|
conf = YamlLintConfig(file=user_global_config)
|
||
|
else:
|
||
|
conf = YamlLintConfig('extends: default')
|
||
|
except YamlLintConfigError as e:
|
||
|
print(e, file=sys.stderr)
|
||
|
sys.exit(-1)
|
||
|
|
||
|
max_level = 0
|
||
|
|
||
|
for file in find_files_recursively(args.files):
|
||
|
filepath = file[2:] if file.startswith('./') else file
|
||
|
try:
|
||
|
with open(file) as f:
|
||
|
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
|
||
|
elif max_level == PROBLEM_LEVELS['warning']:
|
||
|
return_code = 2 if args.strict else 0
|
||
|
else:
|
||
|
return_code = 0
|
||
|
|
||
|
sys.exit(return_code)
|