diff --git a/docs/development.rst b/docs/development.rst index 3dc86a70..7d8907ec 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -4,8 +4,5 @@ Development yamllint provides both a script and a Python module. The latter can be used to write your own linting tools: -.. autoclass:: yamllint.errors.LintProblem - :members: - -.. automodule:: yamllint +.. automodule:: yamllint.linter :members: diff --git a/yamllint/__init__.py b/yamllint/__init__.py index fbc3675e..df9e0f47 100644 --- a/yamllint/__init__.py +++ b/yamllint/__init__.py @@ -14,12 +14,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import yaml - -from yamllint.errors import LintProblem -from yamllint import parser - - APP_NAME = 'yamllint' APP_VERSION = '0.5.1' APP_DESCRIPTION = 'A linter for YAML files.' @@ -28,89 +22,3 @@ __copyright__ = u'Copyright 2016, Adrien Vergé' __license__ = 'GPLv3' __version__ = APP_VERSION - - -def get_costemic_problems(buffer, conf): - rules = conf.enabled_rules() - - # Split token rules from line rules - token_rules = [r for r in rules if r.TYPE == 'token'] - line_rules = [r for r in rules if r.TYPE == 'line'] - - context = {} - for rule in token_rules: - context[rule.ID] = {} - - for elem in parser.token_or_line_generator(buffer): - if isinstance(elem, parser.Token): - for rule in token_rules: - rule_conf = conf.rules[rule.ID] - for problem in rule.check(rule_conf, - elem.curr, elem.prev, elem.next, - context[rule.ID]): - problem.rule = rule.ID - problem.level = rule_conf['level'] - yield problem - elif isinstance(elem, parser.Line): - for rule in line_rules: - rule_conf = conf.rules[rule.ID] - for problem in rule.check(rule_conf, elem): - problem.rule = rule.ID - problem.level = rule_conf['level'] - yield problem - - -def get_syntax_error(buffer): - try: - list(yaml.parse(buffer, Loader=yaml.BaseLoader)) - except yaml.error.MarkedYAMLError as e: - problem = LintProblem(e.problem_mark.line + 1, - e.problem_mark.column + 1, - 'syntax error: ' + e.problem) - problem.level = 'error' - return problem - - -def _lint(buffer, conf): - # If the document contains a syntax error, save it and yield it at the - # right line - syntax_error = get_syntax_error(buffer) - - for problem in get_costemic_problems(buffer, conf): - # Insert the syntax error (if any) at the right place... - if (syntax_error and syntax_error.line <= problem.line and - syntax_error.column <= problem.column): - yield syntax_error - - # If there is already a yamllint error at the same place, discard - # it as it is probably redundant (and maybe it's just a 'warning', - # in which case the script won't even exit with a failure status). - if (syntax_error.line == problem.line and - syntax_error.column == problem.column): - syntax_error = None - continue - - syntax_error = None - - yield problem - - if syntax_error: - yield syntax_error - - -def lint(input, conf): - """Lints a YAML source. - - Returns a generator of LintProblem objects. - - :param input: buffer, string or stream to read from - :param conf: yamllint configuration object - """ - if type(input) == str: - return _lint(input, conf) - elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase - # We need to have everything in memory to parse correctly - content = input.read() - return _lint(content, conf) - else: - raise TypeError('input should be a string or a stream') diff --git a/yamllint/cli.py b/yamllint/cli.py index 0dc8511d..a1f7d68d 100644 --- a/yamllint/cli.py +++ b/yamllint/cli.py @@ -23,7 +23,7 @@ from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION from yamllint.config import YamlLintConfig, YamlLintConfigError -from yamllint import lint +from yamllint import linter def find_files_recursively(items): @@ -96,7 +96,7 @@ def run(argv): try: first = True with open(file) as f: - for problem in lint(f, conf): + for problem in linter.run(f, conf): if args.format == 'parsable': print(Format.parsable(problem, file)) else: diff --git a/yamllint/errors.py b/yamllint/errors.py deleted file mode 100644 index fa2f21b9..00000000 --- a/yamllint/errors.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- 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 . - - -class LintProblem(object): - """Represents a linting problem found by yamllint.""" - def __init__(self, line, column, desc='', rule=None): - #: Line on which the problem was found (starting at 1) - self.line = line - #: Column on which the problem was found (starting at 1) - self.column = column - #: Human-readable description of the problem - self.desc = desc - #: Identifier of the rule that detected the problem - self.rule = rule - self.level = None - - @property - def message(self): - if self.rule is not None: - return '%s (%s)' % (self.desc, self.rule) - return self.desc - - def __eq__(self, other): - return (self.line == other.line and - self.column == other.column and - self.rule == other.rule) - - def __lt__(self, other): - return (self.line < other.line or - (self.line == other.line and self.column < other.column)) - - def __repr__(self): - return '%d:%d: %s' % (self.line, self.column, self.message) - - -class YamlLintError(Exception): - pass - - -class YamlLintConfigError(YamlLintError): - pass diff --git a/yamllint/linter.py b/yamllint/linter.py new file mode 100644 index 00000000..414438e1 --- /dev/null +++ b/yamllint/linter.py @@ -0,0 +1,137 @@ +# -*- 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 . + +import yaml + +from yamllint import parser + + +class LintProblem(object): + """Represents a linting problem found by yamllint.""" + def __init__(self, line, column, desc='', rule=None): + #: Line on which the problem was found (starting at 1) + self.line = line + #: Column on which the problem was found (starting at 1) + self.column = column + #: Human-readable description of the problem + self.desc = desc + #: Identifier of the rule that detected the problem + self.rule = rule + self.level = None + + @property + def message(self): + if self.rule is not None: + return '%s (%s)' % (self.desc, self.rule) + return self.desc + + def __eq__(self, other): + return (self.line == other.line and + self.column == other.column and + self.rule == other.rule) + + def __lt__(self, other): + return (self.line < other.line or + (self.line == other.line and self.column < other.column)) + + def __repr__(self): + return '%d:%d: %s' % (self.line, self.column, self.message) + + +def get_costemic_problems(buffer, conf): + rules = conf.enabled_rules() + + # Split token rules from line rules + token_rules = [r for r in rules if r.TYPE == 'token'] + line_rules = [r for r in rules if r.TYPE == 'line'] + + context = {} + for rule in token_rules: + context[rule.ID] = {} + + for elem in parser.token_or_line_generator(buffer): + if isinstance(elem, parser.Token): + for rule in token_rules: + rule_conf = conf.rules[rule.ID] + for problem in rule.check(rule_conf, + elem.curr, elem.prev, elem.next, + context[rule.ID]): + problem.rule = rule.ID + problem.level = rule_conf['level'] + yield problem + elif isinstance(elem, parser.Line): + for rule in line_rules: + rule_conf = conf.rules[rule.ID] + for problem in rule.check(rule_conf, elem): + problem.rule = rule.ID + problem.level = rule_conf['level'] + yield problem + + +def get_syntax_error(buffer): + try: + list(yaml.parse(buffer, Loader=yaml.BaseLoader)) + except yaml.error.MarkedYAMLError as e: + problem = LintProblem(e.problem_mark.line + 1, + e.problem_mark.column + 1, + 'syntax error: ' + e.problem) + problem.level = 'error' + return problem + + +def _run(buffer, conf): + # If the document contains a syntax error, save it and yield it at the + # right line + syntax_error = get_syntax_error(buffer) + + for problem in get_costemic_problems(buffer, conf): + # Insert the syntax error (if any) at the right place... + if (syntax_error and syntax_error.line <= problem.line and + syntax_error.column <= problem.column): + yield syntax_error + + # If there is already a yamllint error at the same place, discard + # it as it is probably redundant (and maybe it's just a 'warning', + # in which case the script won't even exit with a failure status). + if (syntax_error.line == problem.line and + syntax_error.column == problem.column): + syntax_error = None + continue + + syntax_error = None + + yield problem + + if syntax_error: + yield syntax_error + + +def run(input, conf): + """Lints a YAML source. + + Returns a generator of LintProblem objects. + + :param input: buffer, string or stream to read from + :param conf: yamllint configuration object + """ + if type(input) == str: + return _run(input, conf) + elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase + # We need to have everything in memory to parse correctly + content = input.read() + return _run(content, conf) + else: + raise TypeError('input should be a string or a stream') diff --git a/yamllint/rules/commas.py b/yamllint/rules/commas.py index 3fbd9329..28997eea 100644 --- a/yamllint/rules/commas.py +++ b/yamllint/rules/commas.py @@ -64,7 +64,7 @@ import yaml -from yamllint.errors import LintProblem +from yamllint.linter import LintProblem from yamllint.rules.common import spaces_after, spaces_before diff --git a/yamllint/rules/comments.py b/yamllint/rules/comments.py index 4ad224a5..112f6589 100644 --- a/yamllint/rules/comments.py +++ b/yamllint/rules/comments.py @@ -57,7 +57,7 @@ import yaml -from yamllint.errors import LintProblem +from yamllint.linter import LintProblem from yamllint.rules.common import get_comments_between_tokens diff --git a/yamllint/rules/comments_indentation.py b/yamllint/rules/comments_indentation.py index 6fa80eb0..8bd0c568 100644 --- a/yamllint/rules/comments_indentation.py +++ b/yamllint/rules/comments_indentation.py @@ -77,7 +77,7 @@ import yaml -from yamllint.errors import LintProblem +from yamllint.linter import LintProblem from yamllint.rules.common import get_line_indent, get_comments_between_tokens diff --git a/yamllint/rules/common.py b/yamllint/rules/common.py index 8de3b33d..2f99e848 100644 --- a/yamllint/rules/common.py +++ b/yamllint/rules/common.py @@ -16,7 +16,7 @@ import yaml -from yamllint.errors import LintProblem +from yamllint.linter import LintProblem def spaces_after(token, prev, next, min=-1, max=-1, diff --git a/yamllint/rules/document_end.py b/yamllint/rules/document_end.py index 14417c49..ee62b672 100644 --- a/yamllint/rules/document_end.py +++ b/yamllint/rules/document_end.py @@ -76,7 +76,7 @@ import yaml -from yamllint.errors import LintProblem +from yamllint.linter import LintProblem ID = 'document-end' diff --git a/yamllint/rules/document_start.py b/yamllint/rules/document_start.py index 7e3ff73c..70f511fc 100644 --- a/yamllint/rules/document_start.py +++ b/yamllint/rules/document_start.py @@ -66,7 +66,7 @@ import yaml -from yamllint.errors import LintProblem +from yamllint.linter import LintProblem ID = 'document-start' diff --git a/yamllint/rules/empty_lines.py b/yamllint/rules/empty_lines.py index b8a9158a..6f5496cf 100644 --- a/yamllint/rules/empty_lines.py +++ b/yamllint/rules/empty_lines.py @@ -50,7 +50,7 @@ """ -from yamllint.errors import LintProblem +from yamllint.linter import LintProblem ID = 'empty-lines' diff --git a/yamllint/rules/indentation.py b/yamllint/rules/indentation.py index 3edae82c..e51255fd 100644 --- a/yamllint/rules/indentation.py +++ b/yamllint/rules/indentation.py @@ -132,7 +132,7 @@ import yaml -from yamllint.errors import LintProblem +from yamllint.linter import LintProblem from yamllint.rules.common import is_explicit_key diff --git a/yamllint/rules/line_length.py b/yamllint/rules/line_length.py index 5abb7ba4..a3c2cd7e 100644 --- a/yamllint/rules/line_length.py +++ b/yamllint/rules/line_length.py @@ -41,7 +41,7 @@ """ -from yamllint.errors import LintProblem +from yamllint.linter import LintProblem ID = 'line-length' diff --git a/yamllint/rules/new_line_at_end_of_file.py b/yamllint/rules/new_line_at_end_of_file.py index f5770980..90b1cc2a 100644 --- a/yamllint/rules/new_line_at_end_of_file.py +++ b/yamllint/rules/new_line_at_end_of_file.py @@ -24,7 +24,7 @@ """ -from yamllint.errors import LintProblem +from yamllint.linter import LintProblem ID = 'new-line-at-end-of-file' diff --git a/yamllint/rules/new_lines.py b/yamllint/rules/new_lines.py index d085c9cf..91adb4ef 100644 --- a/yamllint/rules/new_lines.py +++ b/yamllint/rules/new_lines.py @@ -24,7 +24,7 @@ """ -from yamllint.errors import LintProblem +from yamllint.linter import LintProblem ID = 'new-lines' diff --git a/yamllint/rules/trailing_spaces.py b/yamllint/rules/trailing_spaces.py index ec215f83..2fc4bbba 100644 --- a/yamllint/rules/trailing_spaces.py +++ b/yamllint/rules/trailing_spaces.py @@ -39,7 +39,7 @@ import string -from yamllint.errors import LintProblem +from yamllint.linter import LintProblem ID = 'trailing-spaces'