From dbbecb58754ac875a19f312640114a6677076f2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Verg=C3=A9?= Date: Sun, 24 Jan 2016 17:48:20 +0100 Subject: [PATCH] Refactor project layout to import yamllint alone Currently importing yamllint recursively imports its submodules, which finally requires having pyyaml installed. This is a problem when you just want to import APP_VERSION from yamllint. For instance, setup.py imports yamllint to know the version, but doesn't know yet that pyyaml is to be installed, because it is stated in setup.py itself. To solve this, yamllint/__init__.py will only contain constants. The linting functions will be in yamllint/linter.py. --- docs/development.rst | 5 +- yamllint/__init__.py | 92 --------------- yamllint/cli.py | 4 +- yamllint/errors.py | 55 --------- yamllint/linter.py | 137 ++++++++++++++++++++++ yamllint/rules/commas.py | 2 +- yamllint/rules/comments.py | 2 +- yamllint/rules/comments_indentation.py | 2 +- yamllint/rules/common.py | 2 +- yamllint/rules/document_end.py | 2 +- yamllint/rules/document_start.py | 2 +- yamllint/rules/empty_lines.py | 2 +- yamllint/rules/indentation.py | 2 +- yamllint/rules/line_length.py | 2 +- yamllint/rules/new_line_at_end_of_file.py | 2 +- yamllint/rules/new_lines.py | 2 +- yamllint/rules/trailing_spaces.py | 2 +- 17 files changed, 152 insertions(+), 165 deletions(-) delete mode 100644 yamllint/errors.py create mode 100644 yamllint/linter.py diff --git a/docs/development.rst b/docs/development.rst index 3dc86a7..7d8907e 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 fbc3675..df9e0f4 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 @@ __author__ = u'Adrien Vergé' __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 0dc8511..a1f7d68 100644 --- a/yamllint/cli.py +++ b/yamllint/cli.py @@ -23,7 +23,7 @@ import argparse 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 fa2f21b..0000000 --- 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 0000000..414438e --- /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 3fbd932..28997ee 100644 --- a/yamllint/rules/commas.py +++ b/yamllint/rules/commas.py @@ -64,7 +64,7 @@ Use this rule to control the number of spaces before and after commas (``,``). 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 4ad224a..112f658 100644 --- a/yamllint/rules/comments.py +++ b/yamllint/rules/comments.py @@ -57,7 +57,7 @@ Use this rule to control the position and formatting of comments. 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 6fa80eb..8bd0c56 100644 --- a/yamllint/rules/comments_indentation.py +++ b/yamllint/rules/comments_indentation.py @@ -77,7 +77,7 @@ Use this rule to force comments to be indented like content. 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 8de3b33..2f99e84 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 14417c4..ee62b67 100644 --- a/yamllint/rules/document_end.py +++ b/yamllint/rules/document_end.py @@ -76,7 +76,7 @@ Use this rule to require or forbid the use of document end marker (``...``). 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 7e3ff73..70f511f 100644 --- a/yamllint/rules/document_start.py +++ b/yamllint/rules/document_start.py @@ -66,7 +66,7 @@ Use this rule to require or forbid the use of document start marker (``---``). 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 b8a9158..6f5496c 100644 --- a/yamllint/rules/empty_lines.py +++ b/yamllint/rules/empty_lines.py @@ -50,7 +50,7 @@ Use this rule to set a maximal number of allowed consecutive blank lines. """ -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 3edae82..e51255f 100644 --- a/yamllint/rules/indentation.py +++ b/yamllint/rules/indentation.py @@ -132,7 +132,7 @@ Use this rule to control the indentation. 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 5abb7ba..a3c2cd7 100644 --- a/yamllint/rules/line_length.py +++ b/yamllint/rules/line_length.py @@ -41,7 +41,7 @@ Use this rule to set a limit to lines length. """ -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 f577098..90b1cc2 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 @@ this convention too. """ -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 d085c9c..91adb4e 100644 --- a/yamllint/rules/new_lines.py +++ b/yamllint/rules/new_lines.py @@ -24,7 +24,7 @@ Use this rule to force the type of new line characters. """ -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 ec215f8..2fc4bbb 100644 --- a/yamllint/rules/trailing_spaces.py +++ b/yamllint/rules/trailing_spaces.py @@ -39,7 +39,7 @@ Use this rule to forbid trailing spaces at the end of lines. import string -from yamllint.errors import LintProblem +from yamllint.linter import LintProblem ID = 'trailing-spaces'