Compare commits

...

191 Commits

Author SHA1 Message Date
Philipp Huebner
2c6493c2e9 Debian: Package 1.23.0-1 2020-04-19 10:23:36 +02:00
Philipp Huebner
f5d8c765c7 Merge tag 'v1.23.0' into packaging 2020-04-19 10:22:56 +02:00
Philipp Huebner
7cee10f77c Debian: Package 1.21.0-1 2020-04-19 10:11:25 +02:00
Philipp Huebner
d457b37add Merge tag 'v1.21.0' into packaging 2020-04-19 10:10:17 +02:00
Adrien Vergé
a54cbce1b6 yamllint version 1.23.0 2020-04-17 10:31:52 +02:00
Adrien Vergé
b711fd993e quoted-strings: Add options extra-required and extra-allowed
Add ability to:
- require strings to be quoted if they match a pattern (PCRE regex)
- allow quoted strings if they match a pattern, while `require:
  only-when-needed` is enforced.

Co-Authored-By: Leo Feyer (https://github.com/adrienverge/yamllint/pull/246)
2020-04-17 10:29:55 +02:00
Adrien Vergé
d68022b846 config: Allow generic types inside lists
For example it's possible to define a conf like:

    rule:
      foo: [str],
      bar: [int, bool, 'magic'],
2020-04-17 10:29:55 +02:00
Adrien Vergé
851d34b9fd config: Allow rules to validate their configuration 2020-04-17 10:29:55 +02:00
Adrien Vergé
483a8d89a5 yamllint version 1.22.1 2020-04-15 07:55:57 +02:00
Adrien Vergé
fa87913566 quoted-strings: Fix only-when-needed on corner cases
Change implementation of `required: only-when-needed`, because
maintaining a list of `START_TOKENS` and just looking at the first
character of string values has proven to be partially broken.

Cf. discussion at
https://github.com/adrienverge/yamllint/pull/246#issuecomment-612354097.

Fixes https://github.com/adrienverge/yamllint/issues/242 and
https://github.com/adrienverge/yamllint/pull/244.
2020-04-15 07:48:59 +02:00
Adrien Vergé
961c496b4f yamllint version 1.22.0 2020-04-13 14:32:08 +02:00
Adrien Vergé
ce7d3fcc7b quoted-strings: Remove test_quotes_required()
It is exactly the same tests as `test_quote_type_any()`.
2020-04-13 14:28:02 +02:00
Adrien Vergé
0bffba1e13 quoted-strings: Remove test_single_quotes_required()
It is exactly the same tests as `test_quote_type_single()`.
2020-04-13 14:28:02 +02:00
Adrien Vergé
2d8639c3a1 quoted-strings: Fix broken rule for list items
The rule worked for values like:

    flow-map: {a: foo, b: "bar"}
    block-map:
      a: foo
      b: "bar"

But not for:

    flow-seq: [foo, "bar"]
    block-seq:
      - foo
      - "bar"

Also add tests to make sure there will be no regression.

Fixes: #208.
2020-04-13 14:15:29 +02:00
Adrien Vergé
e284d74be1 quoted-strings: Rename tests names for clarity
And move only-when-needed tests at the end for readability.
2020-04-13 14:15:29 +02:00
Adrien Vergé
1a13837e84 docs: Sunset Python 2
Keep supporting Python 2.7 for one extra year after upstream dropped it:
https://www.python.org/doc/sunset-python-2/
2020-04-09 16:29:43 +02:00
Adrien Vergé
46ed0c02be truthy: Add missing test removed from PR
See https://github.com/adrienverge/yamllint/pull/247#discussion_r405421376.
2020-04-08 12:31:12 +02:00
ilyam8
6ce11dedb4 truthy: add check-keys option 2020-04-08 12:26:21 +02:00
Adrien Vergé
542ae758f5 yamllint version 1.21.0 2020-03-24 07:53:14 +01:00
Rui Pinge
3a6a09b7b6 Add support for redundant quotes in quoted-strings rule
Co-Authored-By: Adrien Vergé
2020-03-24 07:44:07 +01:00
Rui Pinge
15aea73fbe Fix quoted-strings rules not working for string values matching scalars 2020-03-14 14:22:29 +01:00
Philipp Huebner
f90ef91ebc Debian: Package 1.20.0-2 2020-02-28 14:19:10 +01:00
Philipp Huebner
0ee814561d Merge tag 'v1.20.0' into packaging 2020-02-28 14:18:08 +01:00
Martin Packman
91763f5476 Fix new-lines rule on Python 3
Use io.open() when reading files in cli which has the same behaviour
in Python 2 and Python 3, and supply the newline='' parameter which
handles but does not translate line endings.

Add dos.yml test file with windows newlines.

Also add to file finding test expected output.

Add test for new-lines rule through the cli.

Validates files are read with the correct universal newlines setting.

Fixes adrienverge/yamllint#228
2020-02-13 12:02:45 +01:00
Martin Packman
5b049e4229 Add RunContext helper for cli tests
Single context manager that includes exit code and output streams.

Use new RunContext throughout test_cli.

Largely non-functional change, saving some repetition of setup.

Also improve some failures by bundling multiple assertions into one.
2020-02-13 12:02:45 +01:00
Adrien Vergé
044c7f0248 cli: Test unicode chars in paths too 2020-01-17 16:01:05 +01:00
Adrien Vergé
734d5d5f73 CI: Run tests on Python 3.8
Python 3.8 was released in October 2019.
2020-01-03 09:33:35 +01:00
dhutty
fd86455076 CI: Disable building on Python 3.4
As can be seen in https://travis-ci.org/adrienverge/yamllint/builds/631325436?utm_source=github_status&utm_medium=notification
The dependency, pathspec, requires Python '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*' but the running Python is 3.4.8

This commit stops Travis building yamllint against 3.4 so that CI can pass again.
2020-01-03 09:29:33 +01:00
Adrien Vergé
13a0f11e7c yamllint version 1.20.0 2019-12-26 16:06:29 +01:00
Sylvestre Ledru
43b95e99d1 Use 'syntax' as rule name upon syntax errors 2019-12-17 19:29:49 +01:00
ffapitalle
8fa9eb3ced Add --no-warnings option to suppress warning messages
Use `--no-warnings` option to hide warning messages. It only shows
problems marked as errors.
2019-12-12 09:12:53 +01:00
Adrien Vergé
da3788e95a yamllint version 1.19.0 2019-11-19 11:28:21 +01:00
Joel Baranick
fb400dc64b Allow disabling all checks for a file
Allow disabling of a file, even if it is invalid YAML (syntax error) by
including `# yamllint disable-file` in the first line.
2019-11-19 11:26:31 +01:00
Adrien Vergé
92324ae730 yamllint version 1.18.0 2019-10-15 09:49:20 +02:00
Imran Iqbal
7359785ea0 fix(default.yaml): disable empty-values & octal-values by default
* Close #204
2019-10-15 09:41:32 +02:00
Hossein Zolfi
579a975b70 docs: Fix pre-commit config file
* pre-commit show warning for unsupported key (sha)
* Demonstrate how to use custom yamllint
2019-10-01 11:36:07 +02:00
Imran Iqbal
f3d9196aa0 docs(configuration): improve yaml-files code example
* A straight copy/paste of the existing example into the `.yamllint` file results in a `yamllint` error!
2019-09-10 19:47:25 +02:00
Ibrahim AshShohail
881d301883 feat: Support reading config from .yamllint.yml and .yamllint.yaml
Signed-off-by: Ibrahim AshShohail <me@ibrasho.com>
2019-08-27 09:49:09 +02:00
Adrien Vergé
b62b424dd4 feat: Lint .yamllint by default 2019-08-26 10:01:40 +02:00
Adrien Vergé
ce0336e430 yamllint version 1.17.0 2019-08-12 16:54:51 +02:00
grzesuav
063c854658 feat: Make YAML file extensions configurable 2019-08-12 16:53:30 +02:00
xatier
673bdbd324 fix(truthy): Fix extra whitespace 2019-08-11 14:50:11 +02:00
Philipp Huebner
790662d369 Debian: Package 1.16.0-1 2019-07-24 17:39:43 +02:00
Philipp Huebner
92c5440d22 Merge tag 'v1.16.0' into packaging 2019-07-24 17:38:51 +02:00
Remi Pointel
cb5fe2c050 add OpenBSD installation instructions. 2019-07-09 10:04:48 +02:00
Adrien Vergé
930c8eea94 docs: Simplify installation instruction in the README 2019-07-07 18:13:43 +02:00
Adrien Vergé
f6a24552d9 yamllint version 1.16.0 2019-06-07 10:04:55 +02:00
Adrien Vergé
0ba193331b truthy: Validate options passed to 'allowed-values'
Make sure values passed in allowed values are correct ones. This is
possible thanks to previous commit, and should prevent users from
writing incorrect configurations.
2019-06-07 09:59:26 +02:00
Adrien Vergé
f65553c4f7 config: Validate config options with list of enums
Allow rules to declare a list of valid values for an option.

For example, a rule like:

    CONF = {'allowed-values': list}

... allowed any value to be passed in the list (including bad ones).

It is now possible to declare:

    CONF = {'allowed-values': ['value1', 'value2', 'value3']}

... so that the list passed to the options must contain only values in
`['value1', 'value2', 'value3']`.
2019-06-07 09:59:26 +02:00
Adrien Vergé
0fef4c14e7 truthy: Try to make docs on allowed-values more explicit
Edit documentation for the `truthy` rule, in order to:
- add quotes to examples (`'yes'` instead of `yes`) to avoid
  misconfigurations,
- group truthy values in the `allowed-values` option paragraph, for
  easier reading.
2019-06-07 09:59:10 +02:00
Ondrej Vaško
4ef7e05f3a truthy: Add allowed-values configuration option
Allows using key `allowed-values` for `truthy` section in configuration file (#150).

This allows to use configuration `truthy: allowed-values: ["yes", "no",
"..."]`, to set custom allowed truthy values.

This is especially useful for people using ansible, where values like
`yes` or `no` are valid and officially supported, but yamllint reports
them as illegal.

Implemented by difference of set of TRUTHY constants and configured
allowed values.

Signed-off-by: Ondrej Vasko <ondrej.vaskoo@gmail.com>
2019-06-06 09:59:00 +02:00
xatier
43c50379e0 Sort import orders 2019-05-27 11:10:30 +02:00
Adrien Vergé
fec2c2fba7 fix(parser): Correctly handle DOS new lines in 'line' rules
Do not consider the trailing `\r` of a line a part of it.
2019-04-09 16:48:00 +02:00
Mateusz Piotrowski
2a66ec2e5e Add FreeBSD installation instructions 2019-03-21 18:40:28 +01:00
Philipp Huebner
cc8d56fb94 Debian: Package 1.15.0-1 2019-02-16 11:00:44 +01:00
Philipp Huebner
d77cb4e2f6 Merge tag 'v1.15.0' into packaging 2019-02-16 10:58:48 +01:00
Adrien Vergé
37700ab3e6 yamllint version 1.15.0 2019-02-11 14:18:11 +01:00
Adrien Vergé
f66661e36d docs(cli): Add a paragraph about standard input
See commit 05dfcbc "cli: Add command line option - to read from standard
input", cc @miguelbarao.
2019-02-11 14:16:33 +01:00
Adrien Vergé
d6b89e94e4 chore(docs): Fix conf.py styling 2019-02-11 14:09:20 +01:00
Miguel Barao
05dfcbc109 cli: Add command line option - to read from standard input
If YAML files are given as arguments, parses these files.
If yamllint is run with - option, stdin.
If no arguments are given, just fail.
2019-02-11 14:04:48 +01:00
Philipp Huebner
1c31952230 Debian: Package 1.14.0-1 2019-01-19 16:11:37 +01:00
Philipp Huebner
8e97e6b151 Merge tag 'v1.14.0' into packaging 2019-01-19 16:10:26 +01:00
Adrien Vergé
16b939958d yamllint version 1.14.0 2019-01-14 09:47:07 +01:00
Adrien Vergé
b4740dc1fb comments: Fix ignore-shebangs option on corner cases 2019-01-14 09:40:31 +01:00
Mattias Bengtsson
b77f78f677 Support ignoring shebangs
Some usages of YAML (like Ansible) supports running the file as a script.

Support (by default) an ignore-shebangs setting for the comments module.

Fixes #116 - comments rule with require-starting-space: true should special case shebang
2019-01-14 09:40:31 +01:00
Adrien Vergé
0f073f7a09 config: Do not require all rule options to be set
Before, it was required to specify all the options when customizing a
rule. For instance, one could use `empty-lines: enable` or `empty-lines:
{max: 1, max-start: 2, max-end: 2}`, but not just `empty-lines: {max:
1}` (it would fail with *invalid config: missing option "max-start" for
rule "empty-lines"*).

This was a minor problem for users, but it prevented the addition of new
options to existing rules, see [1] for an example. If a new option was
added, updating yamllint for all users that customize the rule would
produce a crash (*invalid config: missing option ...*).

To avoid that, let's embed default values inside the rules themselves,
instead of keeping them in `conf/default.yaml`.

This refactor should not have any impact on existing projects. I've
manually checked that it did not change the output of tests, on
different projects:
- ansible/ansible: `test/runner/ansible-test sanity --python 3.7 --test yamllint`
- ansible/molecule: `yamllint -s test/ molecule/`
- Neo23x0/sigma: `make test-yaml`
- markstory/lint-review: `yamllint .`

[1]: https://github.com/adrienverge/yamllint/pull/151
2019-01-10 10:01:31 +01:00
cclauss
bc7ac81707 Travis CI: Add Python 3.7 and 3.8a
* Adds Python 3.7.1 and a current nightly build of Python 3.8 alpha.
* Python 3.3 reached its end of life in 2017 https://devguide.python.org/#branchstatus
2018-12-10 17:12:50 +01:00
Adrien Vergé
a56a1015f0 style(docs): Fix RST lint errors reported by doc8 2018-12-08 11:21:02 +01:00
Adrien Vergé
6cf5eecdac chore(CI): Lint RST (reStructuredText) files 2018-12-08 11:21:02 +01:00
Hugo
f4c56b8216 Upgrade Python syntax with pyupgrade
https://github.com/asottile/pyupgrade
2018-11-26 19:09:47 +01:00
Hugo
5852566ff0 Upgrade unit tests to use more useful asserts 2018-11-26 19:09:47 +01:00
Hugo
4a7986b4cf Remove redundant parentheses 2018-11-26 19:09:47 +01:00
Hugo
3d1ad9a176 Add explicit Trove classifers for PyPI 2018-11-26 19:09:47 +01:00
Hugo
8da6e36bf1 Add python_requires to help pip 2018-11-26 19:09:47 +01:00
Hugo
c281d48507 Drop support for EOL Python 2.6 2018-11-26 19:09:47 +01:00
Adrien Vergé
8bdddf6e89 docs: Warn about Python 2 and problems with line-length
Closes #146.
2018-11-23 14:19:55 +01:00
Adrien Vergé
c8032c086b line-length: Add tests for lines containing unicode characters
Some unicode characters span accross multiple bytes. Python 3 is OK with
that, but Python 2 reports an incorrect number of characters.

Related to https://github.com/adrienverge/yamllint/issues/146
2018-11-23 14:19:55 +01:00
Adrien Vergé
ea045c41b7 CI: Drop Python 3.3 support
The `pkg_resources` package inside `setuptools` explicitly [disallows
Python 3.3](7392f01ffc (diff-81de4a30a55fcc3fb944f8387ea9ec94)):

    if (3, 0) < sys.version_info < (3, 4):
        raise RuntimeError("Python 3.4 or later is required")

It's time to drop support for 3.3.
2018-11-23 14:10:00 +01:00
Philipp Huebner
5cd261d6cb Debian: Package 1.13.0-1 2018-11-18 21:54:52 +01:00
Philipp Huebner
becf9fe98b Merge tag 'v1.13.0' into packaging 2018-11-18 21:53:45 +01:00
Adrien Vergé
c803dd5f6d docs(CHANGELOG): Fix RST format for code snippets 2018-11-14 19:08:39 +01:00
Adrien Vergé
318a12bbe6 yamllint version 1.13.0 2018-11-14 19:04:58 +01:00
Adrien Vergé
66adaee66c docs: Add documentation on the new -f colored option 2018-11-14 19:02:52 +01:00
sedrubal
5062b91cac cli: Add -f colored to force colors
`-f standard` shows non-colored output,
`-f colored` shows colored output,
`-f auto` is the new default, it chooses `standard` or `colored`
depending on terminal capabilities.
2018-10-22 10:35:35 +02:00
sedrubal
3ef85739e3 Use isinstance(x, y) instead of type(x) == y
Fixes pylint C0123.
2018-10-20 10:02:16 +02:00
Adrien Vergé
dc4a9f4fff yamllint version 1.12.1 2018-10-17 10:22:32 +02:00
Adrien Vergé
8354d50016 quoted-strings: Fix broken rule
Original implementation was completely broken. Documentation and actual
behavior were different. Numbers and booleans were detected as wrong, as
well as explicit types.

Fixes #136 and #130.
2018-10-17 10:18:25 +02:00
Adrien Vergé
524d721f0d Update .gitignore to exclude build/ 2018-10-11 15:35:26 +02:00
Adrien Vergé
e864f57d37 docs: Fix missing quoted-strings module in documentation 2018-10-11 15:27:58 +02:00
Adrien Vergé
d41b64aa97 yamllint version 1.12.0 2018-10-04 16:10:56 +02:00
Guido Wischrop (mgm tp)
aaa8777f1d Add quoted-strings rule
* taken from https://github.com/adrienverge/yamllint/pull/110 (submitted by @jurajseffer)
* small fixes for generic and multi-line strings
* fixes for comments from @adrienverge
2018-10-04 16:09:56 +02:00
Adrien Vergé
479f580202 CI: Fix tests failing on Travis for Python 2.6
Because installing dependencies for `coveralls` now fails with:

    Collecting pycparser (from cffi>=1.7->cryptography>=1.3.4; python_version <= "2.7" and extra == "secure"->urllib3[secure]; python_version < "3"->coveralls)
    [...]
    pycparser requires Python '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' but the running Python is 2.6.9
2018-10-02 19:27:54 +02:00
Justin Foreman
e4e99f0aba docs: Update README for CentOS dependency 2018-05-31 11:06:22 +02:00
Adrien Vergé
203cfc20f0 docs: Remove sudo from pip installation instructions 2018-05-09 20:20:18 +02:00
Adrien Vergé
51c30505b5 docs: Add Mac OS installation instructions
See https://github.com/adrienverge/yamllint/issues/91 and
https://github.com/Homebrew/homebrew-core/blob/af2bbe9/Formula/yamllint.rb
2018-05-09 20:19:03 +02:00
Adrien Vergé
ff9ebde608 docs: Remove old Debian / Ubuntu installation instructions 2018-05-09 20:19:03 +02:00
Philipp Huebner
251260c382 Debian: Package 1.11.1-1 2018-05-09 19:22:24 +02:00
Philipp Huebner
c0f7ae1102 Merge tag 'v1.11.1' into packaging 2018-05-09 19:21:30 +02:00
Adrien Vergé
506e066410 yamllint version 1.11.1 2018-04-06 11:11:32 +02:00
Adrien Vergé
54f21c0514 parser: Fix crash with latest PyYAML
There is a backwards-incompatible change in PyYAML that induces a crash
if `check_token()` is not called before `peek_token()`. See commit
a02d17a in PyYAML or https://github.com/yaml/pyyaml/pull/150.

Closes #105.
2018-04-06 11:07:55 +02:00
Adam Johnson
36b4776778 Clarify documentation on the 'truthy' rule
I like the 'truthy' rule but its documentation and message have confused several of my colleagues. I've tried rewriting it to be clearer.
2018-04-06 11:07:46 +02:00
Adrien Vergé
3bdc1b6e1b CI: Don't install Sphinx if Python 2
Recently builds started to fail with:

    Collecting sphinx
      Downloading Sphinx-1.7.2-py2.py3-none-any.whl (1.9MB)
        100% |████████████████████████████████| 1.9MB 731kB/s
    Sphinx requires Python '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*'
    but the running Python is 2.6.9
2018-04-06 09:39:53 +02:00
Adrien Vergé
c16934117b CI: Remove Travis hack for enum34 crashing on Python 3.6
Revert commit 8b9eab3, it is not needed anymore.
2018-04-06 09:39:53 +02:00
Eimert
8ab680635b docs: Make ignore examples clearer
[Solved](https://github.com/metacloud/molecule/issues/1228), when
yamllint is used by molecule.
2018-04-02 20:06:17 +02:00
Philipp Huebner
b47f0f2451 Debian: Package 1.11.0-1 2018-03-06 17:59:45 +01:00
Philipp Huebner
5ca5add463 Merge tag 'v1.11.0' into packaging 2018-03-06 17:59:24 +01:00
Philipp Huebner
542b6738a6 Debian: Package 1.10.0-1 2018-03-06 17:59:14 +01:00
Philipp Huebner
2c3097ca78 Merge tag 'v1.10.0' into packaging 2018-03-06 17:58:19 +01:00
Anthony Sottile
503bde9e70 pre-commit is now served over https! 2018-03-03 08:01:24 +01:00
Nick Burke
1b379628d7 key-duplicates: Handle merge keys (<<)
Merge keys are described here: http://yaml.org/type/merge.html
They shouldn't be considered as duplicated keys.

Fixes https://github.com/adrienverge/yamllint/issues/88
2018-02-28 23:12:43 +01:00
Adrien Vergé
6a842229fd yamllint version 1.11.0 2018-02-21 13:42:06 +01:00
Adrien Vergé
8b9eab33bf CI: Fix failing tests for Python 3.6 because of flake8-import-order
See issue https://github.com/PyCQA/flake8-import-order/issues/149
2018-02-21 13:40:21 +01:00
xieenlong
22e792a433 Feature: checking octal numbers 2017-12-07 18:29:05 +01:00
Adrien Vergé
f713dc8be2 style: Fix E100 and E202 errors reported by pycodestyle 2017-12-07 18:28:53 +01:00
Adrien Vergé
a92743c8ca yamllint version 1.10.0 2017-11-05 10:17:55 +01:00
Adrien Vergé
501def327d tests: Use sys.executable instead of hard-coded 'python'
To test yamllint as a module, tests run commands like
`python -m yamllint`. But some environments (like continuous integration
of Debian or CentOS) don't always include the `python` executable (they
use `python3` instead).

Let's dynamically detect the Python executable path.
2017-11-05 10:06:46 +01:00
Adrien Vergé
ed5d319df8 tests: Use en_US.UTF-8 locale when C.UTF-8 not available
Some operating systems don't have the `C.UTF-8` locale installed yet
(for instance, CentOS 7). In such a case, fallback to `en_US.UTF-8` so
that tests can be run.

This follows commit 92ff315.
2017-11-05 10:02:22 +01:00
Adrien Vergé
6ec1e7b54a Distribution: Include tests in dist file
Since commit e948509 ("setup.py - don't distribute tests"), tests files
are not included in the `.tar.gz` bundle on a fresh repo clone. (On old
repos they were still included, because listed in
`yamllint.egg-info/SOURCES.txt`.)

Let's explicitly include them.
2017-11-05 09:50:46 +01:00
Adrien Vergé
c4475ece34 empty-values: Add forbid-in-flow-mappings conf
This allows preventing implicit `null` from empty values in flow
mappings.

For example:

    {a:}

    {a:, b: 2}

    {
      a: {
        b: ,
        c: {
          d: 4,
          e:
        }
      },
      f:
    }
2017-11-05 09:29:03 +01:00
Greg Dubicki
8537b0a164 Add rule: empty-values, to forbid implicit nulls
only in block mappings for now
2017-11-04 16:22:29 +01:00
Adrien Vergé
83ea74e2f8 CI: Compile documentation on Travis 2017-11-04 16:02:43 +01:00
Waylan Limberg
e43768f203 Better color support check.
Not all systems have `isatty` attribute on `sys.stdout` so check for
existance of attribute before checking value. Also don't use color in
Windows unless environ indicates support. Apparently, Windows can indicate
support by either the presence of `ANSICON` environ variable or if the
`TERM` environ variable is set to `ANSI`. Fixes #79.

No additional tests added, as the relevant tests use fcntl, which is a
Unix only lib. In fact, the tests won't even run in Windows.
2017-10-27 20:06:34 +02:00
Adrien Vergé
d422274563 style: Fix E722 errors reported by pycodestyle
Since a few days ago pycodestyle (formerly called pep8) has a new check:
E722 warning for bare except clauses.

Let's fix our code.
2017-10-27 17:22:35 +02:00
Philipp Huebner
5fbfcb13b1 Debian: Package 1.9.0-1 2017-10-22 09:39:21 +02:00
Philipp Huebner
b8200259a2 Merge tag 'v1.9.0' into packaging 2017-10-22 09:25:28 +02:00
Adrien Vergé
2d931b5a81 yamllint version 1.9.0 2017-10-16 22:52:06 +02:00
Adrien Vergé
773bfc0f3c key-ordering: Add more test cases and documentation 2017-10-16 22:49:39 +02:00
Johannes F. Knauf
1543d0e435 New rule key-ordering
closes #67
2017-10-16 22:49:39 +02:00
Adrien Vergé
f82346dac7 indentation: Add more test cases for key following empty list 2017-10-16 22:17:58 +02:00
Tim Wade
ca540c113b Fix indentation rule for key following empty list
If a key-value pair follows an empty list, i.e.:

```yaml
a:
-
b: c
```

yamllint will complain:

```
warning  wrong indentation: expected 2 but found 0  (indentation)
```

This is because it is expecting the second key to be a continuation of
the block entry above:

```yaml
a:
-
  b: c
```

However, both are perfectly valid, though structurally different.
2017-10-16 22:17:58 +02:00
Adrien Vergé
c8fc170ff0 yamllint version 1.8.2 2017-10-10 12:30:00 +02:00
Philipp Huebner
0fd8b76eea Debian: Package 1.8.1-1 2017-10-04 18:54:47 +02:00
Philipp Huebner
a4ab40c51a Merge tag 'v1.8.1' into packaging 2017-10-04 16:47:16 +02:00
Adrien Vergé
c4a3e15ff0 docs(readthedocs): Fix builds on yamllint.readthedocs.io
Documentation builds on readthedocs.io partly fail because some modules
imported by yammlint cannot be imported in Sphinx automodule.

This commit fixes that using the tip at [1].

Closes #66

[1]: http://docs.readthedocs.io/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules
2017-09-03 16:08:42 +02:00
Sebastian Finke
db57127971 docs(integration): Fix pre-commit config file 2017-08-17 12:07:23 +02:00
blackillzone
c8e516be2f Add documentation for pre-commit 2017-07-19 14:56:21 +02:00
blackillzone
1c0dd48ccd Update pre-commit hook file 2017-07-19 14:56:21 +02:00
Adrien Vergé
f4edb85a04 fix(config): Be clearer about the ignore conf type 2017-07-19 09:48:00 +02:00
Adrien Vergé
d99bb9fec3 yamllint version 1.8.1 2017-07-04 22:23:02 +02:00
Adrien Vergé
3c4013fda1 docs(CHANGELOG): Add a changelog
Closes #57
2017-07-04 22:20:57 +02:00
Adrien Vergé
1a961bd4b0 chore(tests): Also run tests on Python 2.6 2017-07-04 22:07:32 +02:00
Adrien Vergé
7a8cfeed6d chore(deps): Require pathspec >= 0.5.3
This new version adds support for Python 2.6.
2017-07-04 22:07:21 +02:00
Adrien Vergé
f9709bc6e6 yamllint version 1.8.0 2017-06-28 15:30:39 +02:00
Adrien Vergé
5060917e40 style(cli): Space import sections 2017-06-28 15:20:24 +02:00
Adrien Vergé
a052cf7dba chore(tests): Add flake8-import-order linter plugin 2017-06-28 15:18:40 +02:00
Adrien Vergé
ae33716529 chore(tests): Also run tests on Python 3.6 2017-06-28 15:14:46 +02:00
Adrien Vergé
df26cc0438 feat(config): Add support to ignore paths on per-rule basis
Example of configuration to use this feature:

    # For all rules
    ignore: |
      *.dont-lint-me.yaml
      /bin/
      !/bin/*.lint-me-anyway.yaml

    rules:
      key-duplicates:
        ignore: |
          generated
          *.template.yaml
      trailing-spaces:
        ignore: |
          *.ignore-trailing-spaces.yaml
          /ascii-art/*

Closes #43.
2017-06-28 15:14:46 +02:00
Adrien Vergé
342d7b49dd tests(cli): Create a temp test workspace only once
Do not re-create it for every test in the class.
2017-06-28 15:11:24 +02:00
Adrien Vergé
7d638d47b9 tests(cli): Refactor temp test workspace recreation
Make it simpler and re-usable.
2017-06-28 15:11:24 +02:00
Adrien Vergé
db116eaaaf Merge pull request #51 from sedrubal/feature_use-argparse-mutually_exclusive_group
Use argparse mutually_exclusive_group for --config-file and --config-data
2017-05-31 22:43:44 +02:00
sedrubal
30dfa78923 Use argparse mutually_exclusive_group for --config-file and --config-data
This does the same as your solution 😉
2017-05-28 22:59:33 +02:00
Adrien Vergé
4ae829c062 yamllint version 1.7.0 2017-04-25 17:09:50 +02:00
Adrien Vergé
400aa084da Merge pull request #46 from krzysztof-magosa/master
Add information about Emacs integration
2017-03-26 19:49:50 +02:00
Krzysztof Magosa
a825645cbe Add information about Emacs integration 2017-03-25 18:53:09 +01:00
Adrien Vergé
1764e32def Merge pull request #45 from jayvdb/add-__main__
Add __main__
2017-03-21 11:36:58 +01:00
Adrien Vergé
d6a81f1b23 Add tests for python -m yamllint 2017-03-21 11:34:52 +01:00
John Vandenberg
38d14c7314 Add __main__
Allows execution using python -m yamllint
2017-03-21 16:00:58 +07:00
Adrien Vergé
ff1c9ad221 Merge pull request #38 from jhriggs/feature/empty_braces_brackets
Add min-spaces-inside-empty, max-spaces-inside-empty to braces and brackets
2017-03-13 13:38:16 +01:00
Jim Riggs
4b2b57aa32 Rules: Add min-spaces-inside-empty and max-spaces-inside-empty
Add min-spaces-inside-empty and max-spaces-inside-empty to braces and
brackets to allow separate handling for empty and non-empty objects.
2017-03-13 13:36:55 +01:00
Adrien Vergé
51b6d8377f Merge pull request #42 from polyzen/doc-ALE.vim
Doc: Add ALE Vim plugin
2017-02-28 20:42:35 +01:00
Daniel M. Capella
f507319419 Doc: Add ALE Vim plugin 2017-02-28 14:01:25 -05:00
Adrien Vergé
c0c8534501 Merge pull request #40 from jwilk/spelling
Fix typos
2017-02-28 07:59:06 +01:00
Jakub Wilk
2b26cbc56b Fix typos 2017-02-28 00:22:49 +01:00
Adrien Vergé
c037d3e586 yamllint version 1.6.1 2017-02-25 22:43:55 +01:00
Adrien Vergé
228c47ab77 fix(indentation): Fix seq indent detection with consistent spaces
In the case when the conf is as follows:

    indentation:
      spaces: consistent
      indent-sequences: true

and there is no indented block before the first block sequence, and this
block sequence is not indented, then the spaces number is computed as
zero (while it obviously shouldn't be).

This causes such a document to fail on 4th line, instead of 2nd:

    a:
    - b
    c:
      - d

This commit fixes that, and adds corresponding tests.

Fixes: #39
2017-02-25 22:43:55 +01:00
Adrien Vergé
413d7a8e4e Merge pull request #32 from mtnbikenc/typo-fix
Minor cosmetic typo
2017-01-11 21:04:02 +01:00
Russell Teague
c332c8e3d4 Minor cosmetic typo 2017-01-11 13:52:19 -05:00
Adrien Vergé
ea67ba3394 Merge pull request #31 from bootswithdefer/master
support for pre-commit from yelp
2016-12-28 19:13:39 +01:00
bootswithdefer
a7dbfb08b3 support for pre-commit from yelp 2016-12-28 19:11:32 +01:00
Adrien Vergé
42eda54014 yamllint version 1.6.0 2016-11-30 09:37:52 +01:00
Adrien Vergé
e909692f88 docs(truthy): Fix typo 2016-11-18 14:28:46 +01:00
Adrien Vergé
3bc72d4c40 feat(CI): Enforce strict checking of YAML files
Use the `--strict` flag to check all rules on local YAML files, to
prevent all problems (including warnings). This includes the newly added
`truthy` rule.
2016-11-18 12:02:30 +01:00
Adrien Vergé
21e81b6435 fix(rules): Use true/false, not yes/no
Although `yes` and `no` are recognized as booleans by the pyyaml parser,
the correct keywords are `true` and `false` (as highlighted by the newly
added `truthy` rule).

This commit replaces the use of `yes`/`no` by `true`/`false` and
advertise it in the docs, but also makes sure this change is
backward-compatible (so that `yes` and `no` still work).
2016-11-18 12:02:02 +01:00
Adrien Vergé
b97b6ad19b style(tests): Fix new flake8 errors
This change fixes new errors detected by the last version of pycodestyle
(2.2.0), which is a dependency of flake8:

    ./tests/test_spec_examples.py:51:1: E305 expected 2 blank lines
    after class or function definition, found 1
    ./tests/test_spec_examples.py:139:1: E305 expected 2 blank lines
    after class or function definition, found 1

See pycodestyle changelog at 2.2.0 and
https://github.com/PyCQA/pycodestyle/pull/593.
2016-11-17 12:24:38 +01:00
Adrien Vergé
2b7f5c5e72 docs(install): Update Debian version
yamllint is now backported in Debian 8 (Jessie):
https://tracker.debian.org/pkg/yamllint
https://packages.debian.org/source/jessie-backports/yamllint
2016-11-16 18:50:13 +01:00
Adrien Vergé
aeb353159b Packaging: Debian: Package 1.5.0-1 2016-11-09 16:19:15 +01:00
Adrien Vergé
c57d2784df Packaging: Debian: Package 1.3.2-1 2016-11-07 18:42:15 +01:00
Adrien Vergé
c0da5eac54 to include in next Debian release
See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=819866#15
2016-11-07 18:42:15 +01:00
Adrien Vergé
69278a4553 Packaging: Debian: Package 1.2.1-1 2016-11-07 18:42:15 +01:00
Adrien Vergé
e3baeefc81 Packaging: Debian: Package 1.2.0-1 2016-11-07 18:42:15 +01:00
Adrien Vergé
d4d1d78ce6 Packaging: Add links to "new package" bugs for distros 2016-11-07 18:42:15 +01:00
Adrien Vergé
c88cdf5316 Packaging: Update Ubuntu package 2016-11-07 18:42:15 +01:00
Adrien Vergé
1fb673be2e Packaging: Debian: Add man page 2016-11-07 18:42:15 +01:00
Adrien Vergé
ea658ec3d6 Packaging: Add files for Debian 2016-11-07 18:42:14 +01:00
Adrien Vergé
014a6d2db4 Packaging: Add files for Ubuntu packaging (PPA) 2016-11-07 18:42:14 +01:00
Adrien Vergé
64369db9a2 docs(configuration): Fix typo 2016-11-07 18:11:33 +01:00
Adrien Vergé
2428f6eeaf Merge pull request #27 from jsok/strict-mode
Strict mode
2016-11-07 18:10:57 +01:00
Jonathan Sokolowski
bf386b3c90 docs: Explain strict mode return codes 2016-10-24 14:15:23 +11:00
Jonathan Sokolowski
03e0f5aa6b Add strict mode argument to CLI 2016-10-24 14:08:46 +11:00
Adrien Vergé
3b2a73d224 Merge pull request #25 from adamchainz/universal_wheels
Release as a universal wheel
2016-10-14 09:38:10 +02:00
Adam Chainz
3c525ab743 Release as a universal wheel
By releasing as a [Python wheel](http://pythonwheels.com/) as well as a
source distribution, you can speed up end user’s installs. After merging
this command, to release you just need to run `python setup.py clean
sdist bdist_wheel upload`.
2016-10-14 09:37:38 +02:00
88 changed files with 4577 additions and 588 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ __pycache__
/docs/_build /docs/_build
/dist /dist
/yamllint.egg-info /yamllint.egg-info
/build

11
.pre-commit-hooks.yaml Normal file
View File

@@ -0,0 +1,11 @@
---
# For use with pre-commit.
# See usage instructions at https://pre-commit.com
- id: yamllint
name: yamllint
description: This hook runs yamllint.
entry: yamllint
language: python
types: [file, yaml]

View File

@@ -1,17 +1,24 @@
--- ---
dist: xenial # required for Python >= 3.7 (travis-ci/travis-ci#9069)
language: python language: python
python: python:
- 2.7 - 2.7
- 3.3
- 3.4
- 3.5 - 3.5
- 3.6
- 3.7
- 3.8
- nightly - nightly
install: install:
- pip install pyyaml flake8 coveralls - pip install pyyaml coveralls flake8 flake8-import-order doc8
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then pip install sphinx; fi
- pip install . - pip install .
script: script:
- flake8 . - if [[ $TRAVIS_PYTHON_VERSION != nightly ]]; then flake8 .; fi
- yamllint $(git ls-files '*.yaml' '*.yml') - if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then doc8 $(git ls-files '*.rst'); fi
- yamllint --strict $(git ls-files '*.yaml' '*.yml')
- coverage run --source=yamllint setup.py test - coverage run --source=yamllint setup.py test
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then
python setup.py build_sphinx;
fi
after_success: after_success:
coveralls coveralls

145
CHANGELOG.rst Normal file
View File

@@ -0,0 +1,145 @@
Changelog
=========
1.23.0 (2020-04-17)
-------------------
- Allow rules to validate their configuration
- Add options ``extra-required`` and ``extra-allowed`` to ``quoted-strings``
1.22.1 (2020-04-15)
-------------------
- Fix ``quoted-strings`` rule with ``only-when-needed`` on corner cases
1.22.0 (2020-04-13)
-------------------
- Add ``check-keys`` option to the ``truthy`` rule
- Fix ``quoted-strings`` rule not working on sequences items
- Sunset Python 2
1.21.0 (2020-03-24)
-------------------
- Fix ``new-lines`` rule on Python 3 with DOS line endings
- Fix ``quoted-strings`` rule not working for string values matching scalars
- Add ``required: only-when-needed`` option to the ``quoted-strings`` rule
1.20.0 (2019-12-26)
-------------------
- Add --no-warnings option to suppress warning messages
- Use 'syntax' as rule name upon syntax errors
1.19.0 (2019-11-19)
-------------------
- Allow disabling all checks for a file with ``# yamllint disable-file``
1.18.0 (2019-10-15)
-------------------
- Lint ``.yamllint`` config file by default
- Also read config from ``.yamllint.yml`` and ``.yamllint.yaml``
- Improve documentation for ``yaml-files``
- Update documentation for ``pre-commit``
- Explicitly disable ``empty-values`` and ``octal-values`` rules
1.17.0 (2019-08-12)
-------------------
- Simplify installation instructions in the README
- Add OpenBSD installation instructions
- Make YAML file extensions configurable
1.16.0 (2019-06-07)
-------------------
- Add FreeBSD installation instructions
- Fix the ``line`` rule to correctly handle DOS new lines
- Add the ``allowed-values`` option to the ``truthy`` rule
- Allow configuration options to be a list of enums
1.15.0 (2019-02-11)
-------------------
- Allow linting from standard input with ``yamllint -``
1.14.0 (2019-01-14)
-------------------
- Fix documentation code snippets
- Drop Python 2.6 and 3.3 support, add Python 3.7 support
- Update documentation and tests for ``line-length`` + Unicode + Python 2
- Allow rule configurations to lack options
- Add a new ``ignore-shebangs`` option for the ``comments`` rule
1.13.0 (2018-11-14)
-------------------
- Use ``isinstance(x, y)`` instead of ``type(x) == y``
- Add a new ``-f colored`` option
- Update documentation about colored output when run from CLI
1.12.1 (2018-10-17)
-------------------
- Fix the ``quoted-strings`` rule, broken implementation
- Fix missing documentation for the ``quoted-strings`` rule
1.12.0 (2018-10-04)
-------------------
- Add a new ``quoted-strings`` rule
- Update installation documentation for pip, CentOS, Debian, Ubuntu, Mac OS
1.11.1 (2018-04-06)
-------------------
- Handle merge keys (``<<``) in the ``key-duplicates`` rule
- Update documentation about pre-commit
- Make examples for ``ignore`` rule clearer
- Clarify documentation on the 'truthy' rule
- Fix crash in parser due to a change in PyYAML > 3.12
1.11.0 (2018-02-21)
-------------------
- Add a new ``octal-values`` rule
1.10.0 (2017-11-05)
-------------------
- Fix colored output on Windows
- Check documentation compilation on continuous integration
- Add a new ``empty-values`` rule
- Make sure test files are included in dist bundle
- Tests: Use en_US.UTF-8 locale when C.UTF-8 not available
- Tests: Dynamically detect Python executable path
1.9.0 (2017-10-16)
------------------
- Add a new ``key-ordering`` rule
- Fix indentation rule for key following empty list
1.8.2 (2017-10-10)
------------------
- Be clearer about the ``ignore`` conf type
- Update pre-commit hook file
- Add documentation for pre-commit
1.8.1 (2017-07-04)
------------------
- Require pathspec >= 0.5.3
- Support Python 2.6
- Add a changelog
1.8.0 (2017-06-28)
------------------
- Refactor argparse with mutually_exclusive_group
- Add support to ignore paths in configuration

View File

@@ -1,3 +1,4 @@
include LICENSE include LICENSE
include README.rst include README.rst
include docs/* include docs/*
include tests/*.py tests/rules/*.py tests/yaml-1.2-spec-examples/*

View File

@@ -21,6 +21,10 @@ indentation, etc.
Written in Python (compatible with Python 2 & 3). Written in Python (compatible with Python 2 & 3).
⚠ Python 2 upstream support stopped on January 1, 2020. yamllint will keep
best-effort support for Python 2.7 until January 1, 2021. Passed that date,
yamllint will drop all Python 2-related code.
Documentation Documentation
------------- -------------
@@ -38,23 +42,15 @@ Screenshot
Installation Installation
^^^^^^^^^^^^ ^^^^^^^^^^^^
On Fedora / CentOS: Using pip, the Python package manager:
.. code:: bash .. code:: bash
sudo dnf install yamllint pip install --user yamllint
On Debian 9+ / Ubuntu 16.04+: yamllint is also packaged for all major operating systems, see installation
examples (``dnf``, ``apt-get``...) `in the documentation
.. code:: bash <https://yamllint.readthedocs.io/en/stable/quickstart.html>`_.
sudo apt-get install yamllint
Alternatively using pip, the Python package manager:
.. code:: bash
sudo pip install yamllint
Usage Usage
^^^^^ ^^^^^
@@ -119,6 +115,27 @@ or for a whole block:
consectetur : adipiscing elit consectetur : adipiscing elit
# yamllint enable # yamllint enable
Specific files can be ignored (totally or for some rules only) using a
``.gitignore``-style pattern:
.. code:: yaml
# For all rules
ignore: |
*.dont-lint-me.yaml
/bin/
!/bin/*.lint-me-anyway.yaml
rules:
key-duplicates:
ignore: |
generated
*.template.yaml
trailing-spaces:
ignore: |
*.ignore-trailing-spaces.yaml
/ascii-art/*
`Read more in the complete documentation! <https://yamllint.readthedocs.io/>`_ `Read more in the complete documentation! <https://yamllint.readthedocs.io/>`_
License License

View File

@@ -4,6 +4,7 @@
import sys import sys
import os import os
from unittest.mock import MagicMock
sys.path.insert(0, os.path.abspath('..')) # noqa sys.path.insert(0, os.path.abspath('..')) # noqa
@@ -40,3 +41,15 @@ htmlhelp_basename = 'yamllintdoc'
man_pages = [ man_pages = [
('index', 'yamllint', '', [u'Adrien Vergé'], 1) ('index', 'yamllint', '', [u'Adrien Vergé'], 1)
] ]
# -- Build with sphinx automodule without needing to install third-party libs
class Mock(MagicMock):
@classmethod
def __getattr__(cls, name):
return MagicMock()
MOCK_MODULES = ['pathspec', 'yaml']
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)

View File

@@ -14,7 +14,8 @@ To use a custom configuration file, use the ``-c`` option:
If ``-c`` is not provided, yamllint will look for a configuration file in the If ``-c`` is not provided, yamllint will look for a configuration file in the
following locations (by order of preference): following locations (by order of preference):
- ``.yamllint`` in the current working directory - ``.yamllint``, ``.yamllint.yaml`` or ``.yamllint.yml`` in the current working
directory
- ``$XDG_CONFIG_HOME/yamllint/config`` - ``$XDG_CONFIG_HOME/yamllint/config``
- ``~/.config/yamllint/config`` - ``~/.config/yamllint/config``
@@ -45,9 +46,9 @@ It can be chosen using:
Extending the default configuration Extending the default configuration
----------------------------------- -----------------------------------
When writing a custom configuration file, you don't need to redefine every rule. When writing a custom configuration file, you don't need to redefine every
Just extend the ``default`` configuration (or any already-existing configuration rule. Just extend the ``default`` configuration (or any already-existing
file). configuration file).
For instance, if you just want to disable the ``comments-indentation`` rule, For instance, if you just want to disable the ``comments-indentation`` rule,
your file could look like this: your file could look like this:
@@ -102,8 +103,88 @@ Errors and warnings
------------------- -------------------
Problems detected by yamllint can be raised either as errors or as warnings. Problems detected by yamllint can be raised either as errors or as warnings.
The CLI will output them (with different colors when using the ``colored``
output format, or ``auto`` when run from a terminal).
In both cases, the script will output them (with different colors when using the By default the script will exit with a return code ``1`` *only when* there is
``standard`` output format), but the exit code can be different. More precisely, one or more error(s).
the script will exit will a failure code *only when* there is one or more
error(s). However if strict mode is enabled with the ``-s`` (or ``--strict``) option, the
return code will be:
* ``0`` if no errors or warnings occur
* ``1`` if one or more errors occur
* ``2`` if no errors occur, but one or more warnings occur
If the script is invoked with the ``--no-warnings`` option, it won't output
warning level problems, only error level ones.
YAML files extensions
---------------------
To configure what yamllint should consider as YAML files, set ``yaml-files``
configuration option. The default is:
.. code-block:: yaml
yaml-files:
- '*.yaml'
- '*.yml'
- '.yamllint'
The same rules as for ignoring paths apply (``.gitignore``-style path pattern,
see below).
Ignoring paths
--------------
It is possible to exclude specific files or directories, so that the linter
doesn't process them.
You can either totally ignore files (they won't be looked at):
.. code-block:: yaml
extends: default
ignore: |
/this/specific/file.yaml
all/this/directory/
*.template.yaml
or ignore paths only for specific rules:
.. code-block:: yaml
extends: default
rules:
trailing-spaces:
ignore: |
/this-file-has-trailing-spaces-but-it-is-OK.yaml
/generated/*.yaml
Note that this ``.gitignore``-style path pattern allows complex path
exclusion/inclusion, see the `pathspec README file
<https://pypi.python.org/pypi/pathspec>`_ for more details.
Here is a more complex example:
.. code-block:: yaml
# For all rules
ignore: |
*.dont-lint-me.yaml
/bin/
!/bin/*.lint-me-anyway.yaml
extends: default
rules:
key-duplicates:
ignore: |
generated
*.template.yaml
trailing-spaces:
ignore: |
*.ignore-trailing-spaces.yaml
ascii-art/*

View File

@@ -4,9 +4,9 @@ Disable with comments
Disabling checks for a specific line Disabling checks for a specific line
------------------------------------ ------------------------------------
To prevent yamllint from reporting problems for a specific line, add a directive To prevent yamllint from reporting problems for a specific line, add a
comment (``# yamllint disable-line ...``) on that line, or on the line above. directive comment (``# yamllint disable-line ...``) on that line, or on the
For instance: line above. For instance:
.. code-block:: yaml .. code-block:: yaml
@@ -32,7 +32,7 @@ or:
- This line is waaaaaaaaaay too long but yamllint will not report anything about it. - This line is waaaaaaaaaay too long but yamllint will not report anything about it.
This line will be checked by yamllint. This line will be checked by yamllint.
It it possible, although not recommend, to disabled **all** rules for a It is possible, although not recommend, to disabled **all** rules for a
specific line: specific line:
.. code-block:: yaml .. code-block:: yaml
@@ -46,9 +46,9 @@ If you need to disable multiple rules, it is allowed to chain rules like this:
Disabling checks for all (or part of) the file Disabling checks for all (or part of) the file
---------------------------------------------- ----------------------------------------------
To prevent yamllint from reporting problems for the whoe file, or for a block of To prevent yamllint from reporting problems for the whole file, or for a block
lines within the file, use ``# yamllint disable ...`` and ``# yamllint enable of lines within the file, use ``# yamllint disable ...`` and ``# yamllint
...`` directive comments. For instance: enable ...`` directive comments. For instance:
.. code-block:: yaml .. code-block:: yaml
@@ -60,7 +60,7 @@ lines within the file, use ``# yamllint disable ...`` and ``# yamllint enable
- rest of the document... - rest of the document...
It it possible, although not recommend, to disabled **all** rules: It is possible, although not recommend, to disabled **all** rules:
.. code-block:: yaml .. code-block:: yaml
@@ -73,3 +73,31 @@ It it possible, although not recommend, to disabled **all** rules:
If you need to disable multiple rules, it is allowed to chain rules like this: If you need to disable multiple rules, it is allowed to chain rules like this:
``# yamllint disable rule:hyphens rule:commas rule:indentation``. ``# yamllint disable rule:hyphens rule:commas rule:indentation``.
Disabling all checks for a file
-------------------------------
To prevent yamllint from reporting problems for a specific file, add the
directive comment ``# yamllint disable-file`` as the first line of the file.
For instance:
.. code-block:: yaml
# yamllint disable-file
# The following mapping contains the same key twice, but I know what I'm doing:
key: value 1
key: value 2
- This line is waaaaaaaaaay too long but yamllint will not report anything about it.
This line will be checked by yamllint.
or:
.. code-block:: jinja
# yamllint disable-file
# This file is not valid YAML because it is a Jinja template
{% if extra_info %}
key1: value1
{% endif %}
key2: value2

View File

@@ -26,3 +26,4 @@ Table of contents
disable_with_comments disable_with_comments
development development
text_editors text_editors
integration

19
docs/integration.rst Normal file
View File

@@ -0,0 +1,19 @@
Integration with other software
===============================
Integration with pre-commit
---------------------------
You can integrate yamllint in `pre-commit <http://pre-commit.com/>`_ tool.
Here is an example, to add in your .pre-commit-config.yaml
.. code:: yaml
---
# Update the rev variable with the release version that you want, from the yamllint repo
# You can pass your custom .yamllint with args attribute.
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.17.0
hooks:
- id: yamllint
args: [-c=/path/to/.yamllint]

View File

@@ -4,37 +4,49 @@ Quickstart
Installing yamllint Installing yamllint
------------------- -------------------
On Fedora / CentOS: On Fedora / CentOS (note: `EPEL <https://fedoraproject.org/wiki/EPEL>`_ is
required on CentOS):
.. code:: bash .. code:: bash
sudo dnf install yamllint sudo dnf install yamllint
On Debian 9+ / Ubuntu 16.04+: On Debian 8+ / Ubuntu 16.04+:
.. code:: bash .. code:: bash
sudo apt-get install yamllint sudo apt-get install yamllint
On older Debian / Ubuntu versions: On Mac OS 10.11+:
.. code:: bash .. code:: bash
sudo add-apt-repository -y ppa:adrienverge/ppa && sudo apt-get update brew install yamllint
sudo apt-get install yamllint
On FreeBSD:
.. code:: sh
pkg install py36-yamllint
On OpenBSD:
.. code:: sh
doas pkg_add py3-yamllint
Alternatively using pip, the Python package manager: Alternatively using pip, the Python package manager:
.. code:: bash .. code:: bash
sudo pip install yamllint pip install --user yamllint
If you prefer installing from source, you can run, from the source directory: If you prefer installing from source, you can run, from the source directory:
.. code:: bash .. code:: bash
python setup.py sdist python setup.py sdist
sudo pip install dist/yamllint-*.tar.gz pip install --user dist/yamllint-*.tar.gz
Running yamllint Running yamllint
---------------- ----------------
@@ -51,6 +63,12 @@ You can also lint all YAML files in a whole directory:
yamllint . yamllint .
Or lint a YAML stream from standard input:
.. code:: bash
echo -e 'this: is\nvalid: YAML' | yamllint -
The output will look like (colors are not displayed here): The output will look like (colors are not displayed here):
:: ::
@@ -69,6 +87,10 @@ The output will look like (colors are not displayed here):
10:1 error too many blank lines (4 > 2) (empty-lines) 10:1 error too many blank lines (4 > 2) (empty-lines)
11:4 error too many spaces inside braces (braces) 11:4 error too many spaces inside braces (braces)
By default, the output of yamllint is colored when run from a terminal, and
pure text in other cases. Add the ``-f standard`` arguments to force
non-colored output. Use the ``-f colored`` arguments to force colored output.
Add the ``-f parsable`` arguments if you need an output format parsable by a Add the ``-f parsable`` arguments if you need an output format parsable by a
machine (for instance for :doc:`syntax highlighting in text editors machine (for instance for :doc:`syntax highlighting in text editors
<text_editors>`). The output will then look like: <text_editors>`). The output will then look like:

View File

@@ -59,6 +59,11 @@ empty-lines
.. automodule:: yamllint.rules.empty_lines .. automodule:: yamllint.rules.empty_lines
empty-values
------------
.. automodule:: yamllint.rules.empty_values
hyphens hyphens
------- -------
@@ -74,6 +79,11 @@ key-duplicates
.. automodule:: yamllint.rules.key_duplicates .. automodule:: yamllint.rules.key_duplicates
key-ordering
--------------
.. automodule:: yamllint.rules.key_ordering
line-length line-length
----------- -----------
@@ -89,6 +99,16 @@ new-lines
.. automodule:: yamllint.rules.new_lines .. automodule:: yamllint.rules.new_lines
octal-values
------------
.. automodule:: yamllint.rules.octal_values
quoted-strings
--------------
.. automodule:: yamllint.rules.quoted_strings
trailing-spaces trailing-spaces
--------------- ---------------

View File

@@ -9,8 +9,12 @@ text editor.
Vim Vim
--- ---
Assuming that the `syntastic <https://github.com/scrooloose/syntastic>`_ plugin Assuming that the `ALE <https://github.com/w0rp/ale>`_ plugin is
is installed, add to your ``.vimrc``: installed, yamllint is supported by default. It is automatically enabled when
editing YAML files.
If you instead use the `syntastic <https://github.com/scrooloose/syntastic>`_
plugin, add this to your ``.vimrc``:
:: ::
@@ -23,6 +27,12 @@ Assuming that the `neomake <https://github.com/benekastah/neomake>`_ plugin is
installed, yamllint is supported by default. It is automatically enabled when installed, yamllint is supported by default. It is automatically enabled when
editing YAML files. editing YAML files.
Emacs
-----
If you are `flycheck <https://github.com/flycheck/flycheck>`_ user, you can use
`flycheck-yamllint <https://github.com/krzysztof-magosa/flycheck-yamllint>`_ integration.
Other text editors Other text editors
------------------ ------------------

59
packaging/debian/README Normal file
View File

@@ -0,0 +1,59 @@
Release new version for Debian
==============================
VERSION=1.5.0
WD=/tmp/pkg
TARGET=vm-debian8
# Build source archive
python3 setup.py sdist
# Copy files on a Debian system
ssh $TARGET mkdir $WD
rsync -av dist/yamllint-$VERSION.tar.gz $TARGET:$WD/yamllint_$VERSION.orig.tar.gz
ssh $TARGET tar -C $WD -xf $WD/yamllint_$VERSION.orig.tar.gz
if false; then
rsync -av packaging/debian/debian $TARGET:$WD/yamllint-$VERSION
else
ssh $TARGET "cd $WD && sudo apt-get update"
ssh $TARGET "cd $WD && apt-get source yamllint"
ssh $TARGET mv $WD'/yamllint-*/debian' $WD/yamllint-$VERSION # old version
fi
ssh $TARGET
cd $WD/yamllint-$VERSION/debian
# Add a new changelog entry:
dch -v $VERSION-1
# Build packages and sign dsc
debuild -S -sa
# Test if it builds
sudo pbuilder --create
sudo pbuilder --build ../../yamllint_$VERSION-1.dsc
# Run lintian to detect problems
lintian -i -I --show-overrides ../../yamllint_$VERSION-1_source.changes
# Retrieve modified files
rsync -av $TARGET:$WD/yamllint-$VERSION/debian packaging/debian
# Upload to mentors FTP (needed even for updates)
cat >>~/.dput.cf << EOF
[mentors]
fqdn = mentors.debian.net
incoming = /upload
method = http
allow_unsigned_uploads = 0
progress_indicator = 2
# Allow uploads for UNRELEASED packages
allowed_distributions = .*
EOF
dput mentors ../../yamllint_$VERSION-1_source.changes
# Do no upload to Debian FTP: the sponsor will do it
# File a new RFS bug by sending an email
See https://mentors.debian.net/sponsors/rfs-howto/yamllint

View File

@@ -0,0 +1,163 @@
yamllint (1.23.0-1) unstable; urgency=medium
* New upstream version 1.23.0
-- Philipp Huebner <debalance@debian.org> Sun, 19 Apr 2020 10:19:31 +0200
yamllint (1.21.0-1) unstable; urgency=medium
* New upstream version 1.21.0
-- Philipp Huebner <debalance@debian.org> Sat, 11 Apr 2020 17:15:13 +0200
yamllint (1.20.0-2) unstable; urgency=medium
* Removed 'Installing yamllint' section in the manpage (Closes: #947440)
* Moved maintainership into the Python Applications Packaging Team
(Closes: 947416)
-- Philipp Huebner <debalance@debian.org> Fri, 28 Feb 2020 14:02:37 +0100
yamllint (1.20.0-1) unstable; urgency=medium
* New upstream version 1.20.0
* Updated Standards-Version: 4.5.0 (no changes needed)
* Rules-Requires-Root: no
-- Philipp Huebner <debalance@debian.org> Sun, 16 Feb 2020 00:39:28 +0100
yamllint (1.18.0-1) unstable; urgency=medium
* New upstream version 1.18.0
* Added dh-python to build-depends
* Updated Standards-Version: 4.4.1 (no changes needed)
-- Philipp Huebner <debalance@debian.org> Sun, 20 Oct 2019 19:40:24 +0200
yamllint (1.16.0-1) unstable; urgency=medium
* New upstream version 1.16.0
* Updated debhelper compat version: 12
* Updated Standards-Version: 4.4.0 (no changes needed)
-- Philipp Huebner <debalance@debian.org> Wed, 24 Jul 2019 17:35:30 +0200
yamllint (1.15.0-1) unstable; urgency=medium
* New upstream version 1.15.0
-- Philipp Huebner <debalance@debian.org> Sat, 16 Feb 2019 10:59:13 +0100
yamllint (1.14.0-1) unstable; urgency=medium
* New upstream version 1.14.0
* Updated Standards-Version: 4.3.0 (no changes needed)
* Updated debian/copyright
-- Philipp Huebner <debalance@debian.org> Sat, 19 Jan 2019 16:07:26 +0100
yamllint (1.13.0-1) unstable; urgency=medium
* New upstream version 1.13.0
* Updated Standards-Version: 4.2.1 (no changes needed)
-- Philipp Huebner <debalance@debian.org> Sun, 18 Nov 2018 19:16:27 +0100
yamllint (1.11.1-1) unstable; urgency=medium
* New upstream version 1.11.1
* Updated Standards-Version: 4.1.4 (no changes needed)
-- Philipp Huebner <debalance@debian.org> Wed, 09 May 2018 14:24:10 +0200
yamllint (1.11.0-1) unstable; urgency=medium
* New upstream version 1.11.0
* Update Standards-Version: 4.1.3 (no changes needed)
* Switch to debhelper compat level 11
-- Philipp Huebner <debalance@debian.org> Tue, 06 Mar 2018 17:35:44 +0100
yamllint (1.10.0-1) unstable; urgency=medium
* New upstream version 1.10.0
* Drop patches (fixed upstream)
-- Philipp Huebner <debalance@debian.org> Thu, 16 Nov 2017 19:18:18 +0100
yamllint (1.9.0-1) unstable; urgency=medium
* New upstream version 1.9.0
-- Philipp Huebner <debalance@debian.org> Sun, 22 Oct 2017 09:35:39 +0200
yamllint (1.8.1-1) unstable; urgency=medium
* New upstream version 1.8.1
* Add myself as Uploader
* Depend on python3-pkg-resources (Closes: #860075)
* Build-Depend on python3-pathspec
* Add patch to use python3 in tests
* debian/control: set testsuite to autopkgtest-pkg-python
* Update Standards-Version: 4.1.1 (no changes needed)
-- Philipp Huebner <debalance@debian.org> Wed, 04 Oct 2017 18:50:29 +0200
yamllint (1.5.0-1) unstable; urgency=medium
* Output color only on TTY
* Generalize line-length for mappings
* Fix line-length bug by scanning tokens securely
* New rule: truthy
* Fix distribution (don't distribute tests in site-packages)
* Exclude PTY-related test (doesn't work with pbuilder)
-- Adrien Vergé <adrienverge@gmail.com> Mon, 07 Nov 2016 18:51:37 +0100
yamllint (1.3.2-1) unstable; urgency=medium
* Allow disabling yamllint checks using comments
* Detect user config using `os.path.expanduser()`
* Fix non-ASCII comments bug and add tests
* Update standards version to 3.9.8
-- Adrien Vergé <adrienverge@gmail.com> Mon, 27 Jun 2016 22:17:05 +0200
yamllint (1.2.2-1) unstable; urgency=medium
* Update to new upstream version
-- Adrien Vergé <adrienverge@gmail.com> Fri, 24 Jun 2016 08:54:59 +0200
yamllint (1.2.1-1) unstable; urgency=medium
* Update to new upstream version
-- Adrien Vergé <adrienverge@gmail.com> Sun, 03 Apr 2016 11:30:02 +0200
yamllint (1.2.0-1) unstable; urgency=medium
* Update to new upstream version
* Build and include man page in package
* Fix Vcs-* fields in debian/control
* Fix description-synopsis-starts-with-article lintian warning
-- Adrien Vergé <adrienverge@gmail.com> Tue, 08 Mar 2016 08:20:23 +0100
yamllint (1.1.0-1) unstable; urgency=medium
* Update to new upstream version
-- Adrien Vergé <adrienverge@gmail.com> Fri, 04 Mar 2016 18:54:19 +0100
yamllint (1.0.4-1) unstable; urgency=medium
* Update to new upstream version
-- Adrien Vergé <adrienverge@gmail.com> Fri, 04 Mar 2016 12:54:19 +0100
yamllint (1.0.3-1) unstable; urgency=medium
* Initial release (Closes: #816609)
-- Adrien Vergé <adrienverge@gmail.com> Thu, 03 Mar 2016 09:10:38 +0100

View File

@@ -0,0 +1,30 @@
Source: yamllint
Maintainer: Python Applications Packaging Team <python-apps-team@lists.alioth.debian.org>
Uploaders: Adrien Vergé <adrienverge@gmail.com>, Philipp Huebner <debalance@debian.org>
Section: devel
Priority: optional
Build-Depends: debhelper,
debhelper-compat (= 12),
dh-python,
python3-all,
python3-nose,
python3-pathspec,
python3-setuptools,
python3-sphinx,
python3-yaml
Standards-Version: 4.5.0
Rules-Requires-Root: no
Vcs-Browser: https://salsa.debian.org/python-team/applications/yamllint
Vcs-Git: https://salsa.debian.org/python-team/applications/yamllint.git
Homepage: https://github.com/adrienverge/yamllint
Package: yamllint
Architecture: all
Depends: ${misc:Depends},
${python3:Depends},
${shlibs:Depends},
python3-pkg-resources
Description: Linter for YAML files
yamllint does not only check for syntax validity, but for weirdnesses like key
repetition and cosmetic problems such as lines length, trailing spaces,
indentation, etc.

View File

@@ -0,0 +1,29 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: yamllint
Source: https://github.com/adrienverge/yamllint
Files: *
Copyright: 2016-2019 Adrien Vergé <adrienverge@gmail.com>
License: GPL-3+
Files: debian/*
Copyright: 2016-2019 Adrien Vergé <adrienverge@gmail.com>
2017-2019 Philipp Huebner <debalance@debian.org>
License: GPL-3+
License: GPL-3+
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/>.
.
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".

View File

@@ -0,0 +1,62 @@
Description: clean docs
Remove content unsuitable for Debian
Author: Philipp Huebner <debalance@debian.org>
Index: yamllint/docs/quickstart.rst
===================================================================
--- yamllint.orig/docs/quickstart.rst
+++ yamllint/docs/quickstart.rst
@@ -1,53 +1,6 @@
Quickstart
==========
-Installing yamllint
--------------------
-
-On Fedora / CentOS (note: `EPEL <https://fedoraproject.org/wiki/EPEL>`_ is
-required on CentOS):
-
-.. code:: bash
-
- sudo dnf install yamllint
-
-On Debian 8+ / Ubuntu 16.04+:
-
-.. code:: bash
-
- sudo apt-get install yamllint
-
-On Mac OS 10.11+:
-
-.. code:: bash
-
- brew install yamllint
-
-On FreeBSD:
-
-.. code:: sh
-
- pkg install py36-yamllint
-
-On OpenBSD:
-
-.. code:: sh
-
- doas pkg_add py3-yamllint
-
-Alternatively using pip, the Python package manager:
-
-.. code:: bash
-
- pip install --user yamllint
-
-If you prefer installing from source, you can run, from the source directory:
-
-.. code:: bash
-
- python setup.py sdist
- pip install --user dist/yamllint-*.tar.gz
-
Running yamllint
----------------

View File

@@ -0,0 +1 @@
docs.diff

13
packaging/debian/debian/rules Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/make -f
export DH_VERBOSE=1
%:
NOSE_EXCLUDE=test_run_colored_output \
dh $@ --with python3 --buildsystem=pybuild
override_dh_auto_build:
dh_auto_build
PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bman docs build/man
override_dh_installman:
dh_installman build/man/yamllint.1

View File

@@ -0,0 +1 @@
3.0 (quilt)

View File

@@ -0,0 +1,2 @@
yamllint source: debian-watch-does-not-check-gpg-signature
yamllint source: no-dh-sequencer

View File

@@ -0,0 +1,4 @@
Bug-Database: https://github.com/adrienverge/yamllint/issues
FAQ: https://yamllint.readthedocs.org/
Name: yamllint
Repository: https://github.com/adrienverge/yamllint

View File

@@ -0,0 +1,3 @@
# watch control file for uscan
version=3
https://github.com/adrienverge/yamllint/tags .*/archive/v(\d\S*)\.tar\.gz

17
packaging/links Normal file
View File

@@ -0,0 +1,17 @@
Packaging in Fedora
-------------------
https://bugzilla.redhat.com/show_bug.cgi?id=1309907
Packaging in Debian
-------------------
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=816609
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=816611
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=817245
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=819866
Packaging in Ubuntu
-------------------
https://bugs.launchpad.net/ubuntu/+bug/1554406

30
packaging/ubuntu/README Normal file
View File

@@ -0,0 +1,30 @@
Upload new version to PPA
=========================
WORKDIR=/tmp/yamllint-ubuntu
VERSION=1.2.0
git clone -b packaging https://github.com/adrienverge/yamllint.git $WORKDIR
cd $WORKDIR
# Build source archive
python3 setup.py sdist
cd dist
mv yamllint-$VERSION.tar.gz yamllint_$VERSION.orig.tar.gz
tar -xvf yamllint_$VERSION.orig.tar.gz
cp -r ../packaging/ubuntu/debian yamllint-$VERSION/
cd yamllint-$VERSION/debian
# Add a new changelog entry:
dch -v $VERSION-0ubuntu1
# Build packages and sign dsc
debuild -S -sa
# Test if it builds
sudo pbuilder --create
sudo pbuilder --build ../yamllint_$VERSION-0ubuntu1.dsc
# Upload to PPA
dput ppa:adrienverge/ppa ../yamllint_$VERSION-0ubuntu1_source.changes

View File

@@ -0,0 +1,17 @@
yamllint (1.2.0-0ubuntu1) trusty; urgency=medium
* Update to new upstream version
-- Adrien Vergé <adrienverge@gmail.com> Mon, 07 Mar 2016 07:54:19 +0100
yamllint (1.0.3-0ubuntu2) trusty; urgency=medium
* Add python3-nose to build dependencies
-- Adrien Vergé <adrienverge@gmail.com> Wed, 02 Mar 2016 14:12:07 +0100
yamllint (1.0.3-0ubuntu1) trusty; urgency=low
* Initial release
-- Adrien Vergé <adrienverge@gmail.com> Wed, 02 Mar 2016 13:26:29 +0100

View File

@@ -0,0 +1 @@
9

View File

@@ -0,0 +1,27 @@
Source: yamllint
Section: devel
Priority: optional
Maintainer: Adrien Vergé <adrienverge@gmail.com>
Build-Depends:
debhelper (>=9),
python3-all,
python3-nose,
python3-setuptools,
python3-sphinx,
python3-yaml,
Standards-Version: 3.9.7
Homepage: https://github.com/adrienverge/yamllint
Vcs-Git: git://anonscm.debian.org/collab-maint/yamllint.git
Vcs-Browser: https://anonscm.debian.org/cgit/collab-maint/yamllint.git
Package: yamllint
Architecture: any
Depends:
python3-yaml,
${misc:Depends},
${python3:Depends},
${shlibs:Depends},
Description: A linter for YAML files
yamllint does not only check for syntax validity, but for weirdnesses like key
repetition and cosmetic problems such as lines length, trailing spaces,
indentation, etc.

View File

@@ -0,0 +1,19 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: yamllint
Source: https://github.com/adrienverge/yamllint
Files: *
Copyright: 2016 Adrien Vergé <adrienverge@gmail.com>
License: GPL-3+
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/>.

View File

@@ -0,0 +1 @@
README.rst

11
packaging/ubuntu/debian/rules Executable file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/make -f
%:
dh $@ --with python3 --buildsystem=pybuild
override_dh_auto_build:
dh_auto_build
PYTHONPATH=. http_proxy='127.0.0.1:9' sphinx-build -N -bman docs build/man
override_dh_installman:
dh_installman build/man/yamllint.1

View File

@@ -0,0 +1 @@
3.0 (quilt)

View File

@@ -0,0 +1,4 @@
Bug-Database: https://github.com/adrienverge/yamllint/issues
FAQ: https://yamllint.readthedocs.org/
Name: yamllint
Repository: https://github.com/adrienverge/yamllint

View File

@@ -0,0 +1,3 @@
# watch control file for uscan
version=3
https://github.com/adrienverge/yamllint/tags .*/archive/v(\d\S*)\.tar\.gz

12
setup.cfg Normal file
View File

@@ -0,0 +1,12 @@
[bdist_wheel]
universal = 1
[flake8]
import-order-style = pep8
application-import-names = yamllint
[build_sphinx]
all-files = 1
source-dir = docs
build-dir = docs/_build
warning-is-error = 1

View File

@@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from setuptools import setup, find_packages from setuptools import find_packages, setup
from yamllint import (__author__, __license__, from yamllint import (__author__, __license__,
APP_NAME, APP_VERSION, APP_DESCRIPTION) APP_NAME, APP_VERSION, APP_DESCRIPTION)
@@ -29,13 +29,19 @@ setup(
license=__license__, license=__license__,
keywords=['yaml', 'lint', 'linter', 'syntax', 'checker'], keywords=['yaml', 'lint', 'linter', 'syntax', 'checker'],
url='https://github.com/adrienverge/yamllint', url='https://github.com/adrienverge/yamllint',
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
classifiers=[ classifiers=[
'Development Status :: 5 - Production/Stable', 'Development Status :: 5 - Production/Stable',
'Environment :: Console', 'Environment :: Console',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Software Development', 'Topic :: Software Development',
'Topic :: Software Development :: Debuggers', 'Topic :: Software Development :: Debuggers',
'Topic :: Software Development :: Quality Assurance', 'Topic :: Software Development :: Quality Assurance',
@@ -44,9 +50,7 @@ setup(
packages=find_packages(exclude=['tests', 'tests.*']), packages=find_packages(exclude=['tests', 'tests.*']),
entry_points={'console_scripts': ['yamllint=yamllint.cli:run']}, entry_points={'console_scripts': ['yamllint=yamllint.cli:run']},
package_data={'yamllint': ['conf/*.yaml'], package_data={'yamllint': ['conf/*.yaml']},
'tests': ['yaml-1.2-spec-examples/*']}, install_requires=['pathspec >=0.5.3', 'pyyaml'],
install_requires=['pyyaml'], test_suite='tests',
tests_require=['nose'],
test_suite='nose.collector',
) )

View File

@@ -14,6 +14,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import tempfile
import unittest import unittest
import yaml import yaml
@@ -49,3 +51,21 @@ class RuleTestCase(unittest.TestCase):
real_problems = list(linter.run(source, self.build_fake_config(conf))) real_problems = list(linter.run(source, self.build_fake_config(conf)))
self.assertEqual(real_problems, expected_problems) self.assertEqual(real_problems, expected_problems)
def build_temp_workspace(files):
tempdir = tempfile.mkdtemp(prefix='yamllint-tests-')
for path, content in files.items():
path = os.path.join(tempdir, path)
if not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path))
if type(content) is list:
os.mkdir(path)
else:
mode = 'wb' if isinstance(content, bytes) else 'w'
with open(path, mode) as f:
f.write(content)
return tempdir

View File

@@ -32,11 +32,19 @@ class ColonTestCase(RuleTestCase):
'dict7: { a: 1, b, c: 3 }\n', conf) 'dict7: { a: 1, b, c: 3 }\n', conf)
def test_min_spaces(self): def test_min_spaces(self):
conf = 'braces: {max-spaces-inside: -1, min-spaces-inside: 0}' conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: 0\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n' self.check('---\n'
'dict: {}\n', conf) 'dict: {}\n', conf)
conf = 'braces: {max-spaces-inside: -1, min-spaces-inside: 1}' conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n' self.check('---\n'
'dict: {}\n', conf, problem=(2, 8)) 'dict: {}\n', conf, problem=(2, 8))
self.check('---\n' self.check('---\n'
@@ -52,7 +60,11 @@ class ColonTestCase(RuleTestCase):
' b\n' ' b\n'
'}\n', conf) '}\n', conf)
conf = 'braces: {max-spaces-inside: -1, min-spaces-inside: 3}' conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: 3\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n' self.check('---\n'
'dict: { a: 1, b }\n', conf, 'dict: { a: 1, b }\n', conf,
problem1=(2, 9), problem2=(2, 17)) problem1=(2, 9), problem2=(2, 17))
@@ -60,7 +72,11 @@ class ColonTestCase(RuleTestCase):
'dict: { a: 1, b }\n', conf) 'dict: { a: 1, b }\n', conf)
def test_max_spaces(self): def test_max_spaces(self):
conf = 'braces: {max-spaces-inside: 0, min-spaces-inside: -1}' conf = ('braces:\n'
' max-spaces-inside: 0\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n' self.check('---\n'
'dict: {}\n', conf) 'dict: {}\n', conf)
self.check('---\n' self.check('---\n'
@@ -79,7 +95,11 @@ class ColonTestCase(RuleTestCase):
' b\n' ' b\n'
'}\n', conf) '}\n', conf)
conf = 'braces: {max-spaces-inside: 3, min-spaces-inside: -1}' conf = ('braces:\n'
' max-spaces-inside: 3\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n' self.check('---\n'
'dict: { a: 1, b }\n', conf) 'dict: { a: 1, b }\n', conf)
self.check('---\n' self.check('---\n'
@@ -87,7 +107,11 @@ class ColonTestCase(RuleTestCase):
problem1=(2, 11), problem2=(2, 23)) problem1=(2, 11), problem2=(2, 23))
def test_min_and_max_spaces(self): def test_min_and_max_spaces(self):
conf = 'braces: {max-spaces-inside: 0, min-spaces-inside: 0}' conf = ('braces:\n'
' max-spaces-inside: 0\n'
' min-spaces-inside: 0\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n' self.check('---\n'
'dict: {}\n', conf) 'dict: {}\n', conf)
self.check('---\n' self.check('---\n'
@@ -95,14 +119,169 @@ class ColonTestCase(RuleTestCase):
self.check('---\n' self.check('---\n'
'dict: { a: 1, b}\n', conf, problem=(2, 10)) 'dict: { a: 1, b}\n', conf, problem=(2, 10))
conf = 'braces: {max-spaces-inside: 1, min-spaces-inside: 1}' conf = ('braces:\n'
' max-spaces-inside: 1\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n' self.check('---\n'
'dict: {a: 1, b, c: 3 }\n', conf, problem=(2, 8)) 'dict: {a: 1, b, c: 3 }\n', conf, problem=(2, 8))
conf = 'braces: {max-spaces-inside: 2, min-spaces-inside: 0}' conf = ('braces:\n'
' max-spaces-inside: 2\n'
' min-spaces-inside: 0\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n' self.check('---\n'
'dict: {a: 1, b, c: 3 }\n', conf) 'dict: {a: 1, b, c: 3 }\n', conf)
self.check('---\n' self.check('---\n'
'dict: { a: 1, b, c: 3 }\n', conf) 'dict: { a: 1, b, c: 3 }\n', conf)
self.check('---\n' self.check('---\n'
'dict: { a: 1, b, c: 3 }\n', conf, problem=(2, 10)) 'dict: { a: 1, b, c: 3 }\n', conf, problem=(2, 10))
def test_min_spaces_empty(self):
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 0\n'
' min-spaces-inside-empty: 0\n')
self.check('---\n'
'array: {}\n', conf)
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: {}\n', conf, problem=(2, 9))
self.check('---\n'
'array: { }\n', conf)
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: 3\n')
self.check('---\n'
'array: {}\n', conf, problem=(2, 9))
self.check('---\n'
'array: { }\n', conf)
def test_max_spaces_empty(self):
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 0\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: {}\n', conf)
self.check('---\n'
'array: { }\n', conf, problem=(2, 9))
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: {}\n', conf)
self.check('---\n'
'array: { }\n', conf)
self.check('---\n'
'array: { }\n', conf, problem=(2, 10))
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 3\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: {}\n', conf)
self.check('---\n'
'array: { }\n', conf)
self.check('---\n'
'array: { }\n', conf, problem=(2, 12))
def test_min_and_max_spaces_empty(self):
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 2\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: {}\n', conf, problem=(2, 9))
self.check('---\n'
'array: { }\n', conf)
self.check('---\n'
'array: { }\n', conf)
self.check('---\n'
'array: { }\n', conf, problem=(2, 11))
def test_mixed_empty_nonempty(self):
conf = ('braces:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: 0\n'
' min-spaces-inside-empty: 0\n')
self.check('---\n'
'array: { a: 1, b }\n', conf)
self.check('---\n'
'array: {a: 1, b}\n', conf,
problem1=(2, 9), problem2=(2, 16))
self.check('---\n'
'array: {}\n', conf)
self.check('---\n'
'array: { }\n', conf,
problem1=(2, 9))
conf = ('braces:\n'
' max-spaces-inside: 0\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 1\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: { a: 1, b }\n', conf,
problem1=(2, 9), problem2=(2, 17))
self.check('---\n'
'array: {a: 1, b}\n', conf)
self.check('---\n'
'array: {}\n', conf,
problem1=(2, 9))
self.check('---\n'
'array: { }\n', conf)
conf = ('braces:\n'
' max-spaces-inside: 2\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: 1\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: { a: 1, b }\n', conf)
self.check('---\n'
'array: {a: 1, b }\n', conf,
problem1=(2, 9), problem2=(2, 18))
self.check('---\n'
'array: {}\n', conf,
problem1=(2, 9))
self.check('---\n'
'array: { }\n', conf)
self.check('---\n'
'array: { }\n', conf,
problem1=(2, 11))
conf = ('braces:\n'
' max-spaces-inside: 1\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: 1\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: { a: 1, b }\n', conf)
self.check('---\n'
'array: {a: 1, b}\n', conf,
problem1=(2, 9), problem2=(2, 16))
self.check('---\n'
'array: {}\n', conf,
problem1=(2, 9))
self.check('---\n'
'array: { }\n', conf)

View File

@@ -32,11 +32,19 @@ class ColonTestCase(RuleTestCase):
'array7: [ a, b, c ]\n', conf) 'array7: [ a, b, c ]\n', conf)
def test_min_spaces(self): def test_min_spaces(self):
conf = 'brackets: {max-spaces-inside: -1, min-spaces-inside: 0}' conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: 0\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n' self.check('---\n'
'array: []\n', conf) 'array: []\n', conf)
conf = 'brackets: {max-spaces-inside: -1, min-spaces-inside: 1}' conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n' self.check('---\n'
'array: []\n', conf, problem=(2, 9)) 'array: []\n', conf, problem=(2, 9))
self.check('---\n' self.check('---\n'
@@ -51,7 +59,11 @@ class ColonTestCase(RuleTestCase):
' b\n' ' b\n'
']\n', conf) ']\n', conf)
conf = 'brackets: {max-spaces-inside: -1, min-spaces-inside: 3}' conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: 3\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n' self.check('---\n'
'array: [ a, b ]\n', conf, 'array: [ a, b ]\n', conf,
problem1=(2, 10), problem2=(2, 15)) problem1=(2, 10), problem2=(2, 15))
@@ -59,7 +71,11 @@ class ColonTestCase(RuleTestCase):
'array: [ a, b ]\n', conf) 'array: [ a, b ]\n', conf)
def test_max_spaces(self): def test_max_spaces(self):
conf = 'brackets: {max-spaces-inside: 0, min-spaces-inside: -1}' conf = ('brackets:\n'
' max-spaces-inside: 0\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n' self.check('---\n'
'array: []\n', conf) 'array: []\n', conf)
self.check('---\n' self.check('---\n'
@@ -78,7 +94,11 @@ class ColonTestCase(RuleTestCase):
' b\n' ' b\n'
']\n', conf) ']\n', conf)
conf = 'brackets: {max-spaces-inside: 3, min-spaces-inside: -1}' conf = ('brackets:\n'
' max-spaces-inside: 3\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n' self.check('---\n'
'array: [ a, b ]\n', conf) 'array: [ a, b ]\n', conf)
self.check('---\n' self.check('---\n'
@@ -86,7 +106,11 @@ class ColonTestCase(RuleTestCase):
problem1=(2, 12), problem2=(2, 21)) problem1=(2, 12), problem2=(2, 21))
def test_min_and_max_spaces(self): def test_min_and_max_spaces(self):
conf = 'brackets: {max-spaces-inside: 0, min-spaces-inside: 0}' conf = ('brackets:\n'
' max-spaces-inside: 0\n'
' min-spaces-inside: 0\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n' self.check('---\n'
'array: []\n', conf) 'array: []\n', conf)
self.check('---\n' self.check('---\n'
@@ -94,14 +118,169 @@ class ColonTestCase(RuleTestCase):
self.check('---\n' self.check('---\n'
'array: [ a, b]\n', conf, problem=(2, 11)) 'array: [ a, b]\n', conf, problem=(2, 11))
conf = 'brackets: {max-spaces-inside: 1, min-spaces-inside: 1}' conf = ('brackets:\n'
' max-spaces-inside: 1\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n' self.check('---\n'
'array: [a, b, c ]\n', conf, problem=(2, 9)) 'array: [a, b, c ]\n', conf, problem=(2, 9))
conf = 'brackets: {max-spaces-inside: 2, min-spaces-inside: 0}' conf = ('brackets:\n'
' max-spaces-inside: 2\n'
' min-spaces-inside: 0\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n' self.check('---\n'
'array: [a, b, c ]\n', conf) 'array: [a, b, c ]\n', conf)
self.check('---\n' self.check('---\n'
'array: [ a, b, c ]\n', conf) 'array: [ a, b, c ]\n', conf)
self.check('---\n' self.check('---\n'
'array: [ a, b, c ]\n', conf, problem=(2, 11)) 'array: [ a, b, c ]\n', conf, problem=(2, 11))
def test_min_spaces_empty(self):
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 0\n'
' min-spaces-inside-empty: 0\n')
self.check('---\n'
'array: []\n', conf)
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: []\n', conf, problem=(2, 9))
self.check('---\n'
'array: [ ]\n', conf)
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: -1\n'
' min-spaces-inside-empty: 3\n')
self.check('---\n'
'array: []\n', conf, problem=(2, 9))
self.check('---\n'
'array: [ ]\n', conf)
def test_max_spaces_empty(self):
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 0\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: []\n', conf)
self.check('---\n'
'array: [ ]\n', conf, problem=(2, 9))
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 1\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: []\n', conf)
self.check('---\n'
'array: [ ]\n', conf)
self.check('---\n'
'array: [ ]\n', conf, problem=(2, 10))
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 3\n'
' min-spaces-inside-empty: -1\n')
self.check('---\n'
'array: []\n', conf)
self.check('---\n'
'array: [ ]\n', conf)
self.check('---\n'
'array: [ ]\n', conf, problem=(2, 12))
def test_min_and_max_spaces_empty(self):
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 2\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: []\n', conf, problem=(2, 9))
self.check('---\n'
'array: [ ]\n', conf)
self.check('---\n'
'array: [ ]\n', conf)
self.check('---\n'
'array: [ ]\n', conf, problem=(2, 11))
def test_mixed_empty_nonempty(self):
conf = ('brackets:\n'
' max-spaces-inside: -1\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: 0\n'
' min-spaces-inside-empty: 0\n')
self.check('---\n'
'array: [ a, b ]\n', conf)
self.check('---\n'
'array: [a, b]\n', conf,
problem1=(2, 9), problem2=(2, 13))
self.check('---\n'
'array: []\n', conf)
self.check('---\n'
'array: [ ]\n', conf,
problem1=(2, 9))
conf = ('brackets:\n'
' max-spaces-inside: 0\n'
' min-spaces-inside: -1\n'
' max-spaces-inside-empty: 1\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: [ a, b ]\n', conf,
problem1=(2, 9), problem2=(2, 14))
self.check('---\n'
'array: [a, b]\n', conf)
self.check('---\n'
'array: []\n', conf,
problem1=(2, 9))
self.check('---\n'
'array: [ ]\n', conf)
conf = ('brackets:\n'
' max-spaces-inside: 2\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: 1\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: [ a, b ]\n', conf)
self.check('---\n'
'array: [a, b ]\n', conf,
problem1=(2, 9), problem2=(2, 15))
self.check('---\n'
'array: []\n', conf,
problem1=(2, 9))
self.check('---\n'
'array: [ ]\n', conf)
self.check('---\n'
'array: [ ]\n', conf,
problem1=(2, 11))
conf = ('brackets:\n'
' max-spaces-inside: 1\n'
' min-spaces-inside: 1\n'
' max-spaces-inside-empty: 1\n'
' min-spaces-inside-empty: 1\n')
self.check('---\n'
'array: [ a, b ]\n', conf)
self.check('---\n'
'array: [a, b]\n', conf,
problem1=(2, 9), problem2=(2, 13))
self.check('---\n'
'array: []\n', conf,
problem1=(2, 9))
self.check('---\n'
'array: [ ]\n', conf)

View File

@@ -43,7 +43,7 @@ class CommentsTestCase(RuleTestCase):
def test_starting_space(self): def test_starting_space(self):
conf = ('comments:\n' conf = ('comments:\n'
' require-starting-space: yes\n' ' require-starting-space: true\n'
' min-spaces-from-content: -1\n' ' min-spaces-from-content: -1\n'
'comments-indentation: disable\n') 'comments-indentation: disable\n')
self.check('---\n' self.check('---\n'
@@ -80,9 +80,51 @@ class CommentsTestCase(RuleTestCase):
problem3=(9, 2), problem4=(10, 4), problem3=(9, 2), problem4=(10, 4),
problem5=(15, 3)) problem5=(15, 3))
def test_shebang(self):
conf = ('comments:\n'
' require-starting-space: true\n'
' ignore-shebangs: false\n'
'comments-indentation: disable\n'
'document-start: disable\n')
self.check('#!/bin/env my-interpreter\n',
conf, problem1=(1, 2))
self.check('# comment\n'
'#!/bin/env my-interpreter\n', conf,
problem1=(2, 2))
self.check('#!/bin/env my-interpreter\n'
'---\n'
'#comment\n'
'#!/bin/env my-interpreter\n'
'', conf,
problem1=(1, 2), problem2=(3, 2), problem3=(4, 2))
self.check('#! not a shebang\n',
conf, problem1=(1, 2))
self.check('key: #!/not/a/shebang\n',
conf, problem1=(1, 8))
def test_ignore_shebang(self):
conf = ('comments:\n'
' require-starting-space: true\n'
' ignore-shebangs: true\n'
'comments-indentation: disable\n'
'document-start: disable\n')
self.check('#!/bin/env my-interpreter\n', conf)
self.check('# comment\n'
'#!/bin/env my-interpreter\n', conf,
problem1=(2, 2))
self.check('#!/bin/env my-interpreter\n'
'---\n'
'#comment\n'
'#!/bin/env my-interpreter\n', conf,
problem2=(3, 2), problem3=(4, 2))
self.check('#! not a shebang\n',
conf, problem1=(1, 2))
self.check('key: #!/not/a/shebang\n',
conf, problem1=(1, 8))
def test_spaces_from_content(self): def test_spaces_from_content(self):
conf = ('comments:\n' conf = ('comments:\n'
' require-starting-space: no\n' ' require-starting-space: false\n'
' min-spaces-from-content: 2\n') ' min-spaces-from-content: 2\n')
self.check('---\n' self.check('---\n'
'# comment\n' '# comment\n'
@@ -104,7 +146,7 @@ class CommentsTestCase(RuleTestCase):
def test_both(self): def test_both(self):
conf = ('comments:\n' conf = ('comments:\n'
' require-starting-space: yes\n' ' require-starting-space: true\n'
' min-spaces-from-content: 2\n' ' min-spaces-from-content: 2\n'
'comments-indentation: disable\n') 'comments-indentation: disable\n')
self.check('---\n' self.check('---\n'
@@ -134,7 +176,7 @@ class CommentsTestCase(RuleTestCase):
def test_empty_comment(self): def test_empty_comment(self):
conf = ('comments:\n' conf = ('comments:\n'
' require-starting-space: yes\n' ' require-starting-space: true\n'
' min-spaces-from-content: 2\n') ' min-spaces-from-content: 2\n')
self.check('---\n' self.check('---\n'
'# This is paragraph 1.\n' '# This is paragraph 1.\n'
@@ -146,13 +188,13 @@ class CommentsTestCase(RuleTestCase):
def test_first_line(self): def test_first_line(self):
conf = ('comments:\n' conf = ('comments:\n'
' require-starting-space: yes\n' ' require-starting-space: true\n'
' min-spaces-from-content: 2\n') ' min-spaces-from-content: 2\n')
self.check('# comment\n', conf) self.check('# comment\n', conf)
def test_last_line(self): def test_last_line(self):
conf = ('comments:\n' conf = ('comments:\n'
' require-starting-space: yes\n' ' require-starting-space: true\n'
' min-spaces-from-content: 2\n' ' min-spaces-from-content: 2\n'
'new-line-at-end-of-file: disable\n') 'new-line-at-end-of-file: disable\n')
self.check('# comment with no newline char:\n' self.check('# comment with no newline char:\n'
@@ -160,7 +202,7 @@ class CommentsTestCase(RuleTestCase):
def test_multi_line_scalar(self): def test_multi_line_scalar(self):
conf = ('comments:\n' conf = ('comments:\n'
' require-starting-space: yes\n' ' require-starting-space: true\n'
' min-spaces-from-content: 2\n' ' min-spaces-from-content: 2\n'
'trailing-spaces: disable\n') 'trailing-spaces: disable\n')
self.check('---\n' self.check('---\n'

View File

@@ -31,7 +31,7 @@ class DocumentEndTestCase(RuleTestCase):
' document: end\n', conf) ' document: end\n', conf)
def test_required(self): def test_required(self):
conf = 'document-end: {present: yes}' conf = 'document-end: {present: true}'
self.check('', conf) self.check('', conf)
self.check('\n', conf) self.check('\n', conf)
self.check('---\n' self.check('---\n'
@@ -43,7 +43,7 @@ class DocumentEndTestCase(RuleTestCase):
' document: end\n', conf, problem=(3, 1)) ' document: end\n', conf, problem=(3, 1))
def test_forbidden(self): def test_forbidden(self):
conf = 'document-end: {present: no}' conf = 'document-end: {present: false}'
self.check('---\n' self.check('---\n'
'with:\n' 'with:\n'
' document: end\n' ' document: end\n'
@@ -53,7 +53,7 @@ class DocumentEndTestCase(RuleTestCase):
' document: end\n', conf) ' document: end\n', conf)
def test_multiple_documents(self): def test_multiple_documents(self):
conf = ('document-end: {present: yes}\n' conf = ('document-end: {present: true}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('---\n' self.check('---\n'
'first: document\n' 'first: document\n'

View File

@@ -28,7 +28,7 @@ class DocumentStartTestCase(RuleTestCase):
'key: val\n', conf) 'key: val\n', conf)
def test_required(self): def test_required(self):
conf = ('document-start: {present: yes}\n' conf = ('document-start: {present: true}\n'
'empty-lines: disable\n') 'empty-lines: disable\n')
self.check('', conf) self.check('', conf)
self.check('\n', conf) self.check('\n', conf)
@@ -44,7 +44,7 @@ class DocumentStartTestCase(RuleTestCase):
'key: val\n', conf) 'key: val\n', conf)
def test_forbidden(self): def test_forbidden(self):
conf = ('document-start: {present: no}\n' conf = ('document-start: {present: false}\n'
'empty-lines: disable\n') 'empty-lines: disable\n')
self.check('', conf) self.check('', conf)
self.check('key: val\n', conf) self.check('key: val\n', conf)
@@ -62,7 +62,7 @@ class DocumentStartTestCase(RuleTestCase):
'key: val\n', conf, problem=(2, 1)) 'key: val\n', conf, problem=(2, 1))
def test_multiple_documents(self): def test_multiple_documents(self):
conf = 'document-start: {present: yes}' conf = 'document-start: {present: true}'
self.check('---\n' self.check('---\n'
'first: document\n' 'first: document\n'
'...\n' '...\n'
@@ -85,7 +85,7 @@ class DocumentStartTestCase(RuleTestCase):
'third: document\n', conf, problem=(4, 1, 'syntax')) 'third: document\n', conf, problem=(4, 1, 'syntax'))
def test_directives(self): def test_directives(self):
conf = 'document-start: {present: yes}' conf = 'document-start: {present: true}'
self.check('%YAML 1.2\n' self.check('%YAML 1.2\n'
'---\n' '---\n'
'doc: ument\n' 'doc: ument\n'

View File

@@ -78,3 +78,22 @@ class EmptyLinesTestCase(RuleTestCase):
'document-start: disable\n') 'document-start: disable\n')
self.check('non empty\n', conf) self.check('non empty\n', conf)
self.check('non empty\n\n', conf, problem=(2, 1)) self.check('non empty\n\n', conf, problem=(2, 1))
def test_with_dos_newlines(self):
conf = ('empty-lines: {max: 2, max-start: 0, max-end: 0}\n'
'new-lines: {type: dos}\n'
'document-start: disable\n')
self.check('---\r\n', conf)
self.check('---\r\ntext\r\n\r\ntext\r\n', conf)
self.check('\r\n---\r\ntext\r\n\r\ntext\r\n', conf,
problem=(1, 1))
self.check('\r\n\r\n\r\n---\r\ntext\r\n\r\ntext\r\n', conf,
problem=(3, 1))
self.check('---\r\ntext\r\n\r\n\r\n\r\ntext\r\n', conf,
problem=(5, 1))
self.check('---\r\ntext\r\n\r\n\r\n\r\n\r\n\r\n\r\ntext\r\n', conf,
problem=(8, 1))
self.check('---\r\ntext\r\n\r\ntext\r\n\r\n', conf,
problem=(5, 1))
self.check('---\r\ntext\r\n\r\ntext\r\n\r\n\r\n\r\n', conf,
problem=(7, 1))

View File

@@ -0,0 +1,261 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Greg Dubicki
#
# 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 tests.common import RuleTestCase
class EmptyValuesTestCase(RuleTestCase):
rule_id = 'empty-values'
def test_disabled(self):
conf = ('empty-values: disable\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'foo:\n', conf)
self.check('---\n'
'foo:\n'
' bar:\n', conf)
self.check('---\n'
'{a:}\n', conf)
self.check('---\n'
'foo: {a:}\n', conf)
self.check('---\n'
'- {a:}\n'
'- {a:, b: 2}\n'
'- {a: 1, b:}\n'
'- {a: 1, b: , }\n', conf)
self.check('---\n'
'{a: {b: , c: {d: 4, e:}}, f:}\n', conf)
def test_in_block_mappings_disabled(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n', conf)
self.check('---\n'
'foo:\n'
'bar: aaa\n', conf)
def test_in_block_mappings_single_line(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'implicitly-null:\n', conf, problem1=(2, 17))
self.check('---\n'
'implicitly-null:with-colons:in-key:\n', conf,
problem1=(2, 36))
self.check('---\n'
'implicitly-null:with-colons:in-key2:\n', conf,
problem1=(2, 37))
def test_in_block_mappings_all_lines(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
'bar:\n'
'foobar:\n', conf, problem1=(2, 5),
problem2=(3, 5), problem3=(4, 8))
def test_in_block_mappings_explicit_end_of_document(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
'...\n', conf, problem1=(2, 5))
def test_in_block_mappings_not_end_of_document(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
'bar:\n'
' aaa\n', conf, problem1=(2, 5))
def test_in_block_mappings_different_level(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
' bar:\n'
'aaa: bbb\n', conf, problem1=(3, 6))
def test_in_block_mappings_empty_flow_mapping(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'foo: {a:}\n', conf)
self.check('---\n'
'- {a:, b: 2}\n'
'- {a: 1, b:}\n'
'- {a: 1, b: , }\n', conf)
def test_in_block_mappings_empty_block_sequence(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
' -\n', conf)
def test_in_block_mappings_not_empty_or_explicit_null(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'foo:\n'
' bar:\n'
' aaa\n', conf)
self.check('---\n'
'explicitly-null: null\n', conf)
self.check('---\n'
'explicitly-null:with-colons:in-key: null\n', conf)
self.check('---\n'
'false-null: nulL\n', conf)
self.check('---\n'
'empty-string: \'\'\n', conf)
self.check('---\n'
'nullable-boolean: false\n', conf)
self.check('---\n'
'nullable-int: 0\n', conf)
self.check('---\n'
'First occurrence: &anchor Foo\n'
'Second occurrence: *anchor\n', conf)
def test_in_block_mappings_various_explicit_null(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n')
self.check('---\n'
'null-alias: ~\n', conf)
self.check('---\n'
'null-key1: {?: val}\n', conf)
self.check('---\n'
'null-key2: {? !!null "": val}\n', conf)
def test_in_block_mappings_comments(self):
conf = ('empty-values: {forbid-in-block-mappings: true,\n'
' forbid-in-flow-mappings: false}\n'
'comments: disable\n')
self.check('---\n'
'empty: # comment\n'
'foo:\n'
' bar: # comment\n', conf,
problem1=(2, 7),
problem2=(4, 7))
def test_in_flow_mappings_disabled(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: false}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'{a:}\n', conf)
self.check('---\n'
'foo: {a:}\n', conf)
self.check('---\n'
'- {a:}\n'
'- {a:, b: 2}\n'
'- {a: 1, b:}\n'
'- {a: 1, b: , }\n', conf)
self.check('---\n'
'{a: {b: , c: {d: 4, e:}}, f:}\n', conf)
def test_in_flow_mappings_single_line(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: true}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'{a:}\n', conf,
problem=(2, 4))
self.check('---\n'
'foo: {a:}\n', conf,
problem=(2, 9))
self.check('---\n'
'- {a:}\n'
'- {a:, b: 2}\n'
'- {a: 1, b:}\n'
'- {a: 1, b: , }\n', conf,
problem1=(2, 6),
problem2=(3, 6),
problem3=(4, 12),
problem4=(5, 12))
self.check('---\n'
'{a: {b: , c: {d: 4, e:}}, f:}\n', conf,
problem1=(2, 8),
problem2=(2, 23),
problem3=(2, 29))
def test_in_flow_mappings_multi_line(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: true}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'foo: {\n'
' a:\n'
'}\n', conf,
problem=(3, 5))
self.check('---\n'
'{\n'
' a: {\n'
' b: ,\n'
' c: {\n'
' d: 4,\n'
' e:\n'
' }\n'
' },\n'
' f:\n'
'}\n', conf,
problem1=(4, 7),
problem2=(7, 9),
problem3=(10, 5))
def test_in_flow_mappings_various_explicit_null(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: true}\n'
'braces: disable\n'
'commas: disable\n')
self.check('---\n'
'{explicit-null: null}\n', conf)
self.check('---\n'
'{null-alias: ~}\n', conf)
self.check('---\n'
'null-key1: {?: val}\n', conf)
self.check('---\n'
'null-key2: {? !!null "": val}\n', conf)
def test_in_flow_mappings_comments(self):
conf = ('empty-values: {forbid-in-block-mappings: false,\n'
' forbid-in-flow-mappings: true}\n'
'braces: disable\n'
'commas: disable\n'
'comments: disable\n')
self.check('---\n'
'{\n'
' a: {\n'
' b: , # comment\n'
' c: {\n'
' d: 4, # comment\n'
' e: # comment\n'
' }\n'
' },\n'
' f: # comment\n'
'}\n', conf,
problem1=(4, 7),
problem2=(7, 9),
problem3=(10, 5))

View File

@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from tests.common import RuleTestCase from tests.common import RuleTestCase
from yamllint.parser import token_or_comment_generator, Comment from yamllint.parser import token_or_comment_generator, Comment
from yamllint.rules.indentation import check from yamllint.rules.indentation import check
@@ -50,8 +51,8 @@ class IndentationStackTestCase(RuleTestCase):
.replace('Mapping', 'Map')) .replace('Mapping', 'Map'))
if token_type in ('StreamStart', 'StreamEnd'): if token_type in ('StreamStart', 'StreamEnd'):
continue continue
output += '%9s %s\n' % (token_type, output += '{:>9} {}\n'.format(token_type,
self.format_stack(context['stack'])) self.format_stack(context['stack']))
return output return output
def test_simple_mapping(self): def test_simple_mapping(self):
@@ -549,7 +550,7 @@ class IndentationTestCase(RuleTestCase):
'...\n', conf) '...\n', conf)
def test_one_space(self): def test_one_space(self):
conf = 'indentation: {spaces: 1, indent-sequences: no}' conf = 'indentation: {spaces: 1, indent-sequences: false}'
self.check('---\n' self.check('---\n'
'object:\n' 'object:\n'
' k1:\n' ' k1:\n'
@@ -562,7 +563,7 @@ class IndentationTestCase(RuleTestCase):
' - name: Linux\n' ' - name: Linux\n'
' date: 1991\n' ' date: 1991\n'
'...\n', conf) '...\n', conf)
conf = 'indentation: {spaces: 1, indent-sequences: yes}' conf = 'indentation: {spaces: 1, indent-sequences: true}'
self.check('---\n' self.check('---\n'
'object:\n' 'object:\n'
' k1:\n' ' k1:\n'
@@ -577,7 +578,7 @@ class IndentationTestCase(RuleTestCase):
'...\n', conf) '...\n', conf)
def test_two_spaces(self): def test_two_spaces(self):
conf = 'indentation: {spaces: 2, indent-sequences: no}' conf = 'indentation: {spaces: 2, indent-sequences: false}'
self.check('---\n' self.check('---\n'
'object:\n' 'object:\n'
' k1:\n' ' k1:\n'
@@ -589,8 +590,11 @@ class IndentationTestCase(RuleTestCase):
' date: 1969\n' ' date: 1969\n'
' - name: Linux\n' ' - name: Linux\n'
' date: 1991\n' ' date: 1991\n'
' k4:\n'
' -\n'
' k5: v3\n'
'...\n', conf) '...\n', conf)
conf = 'indentation: {spaces: 2, indent-sequences: yes}' conf = 'indentation: {spaces: 2, indent-sequences: true}'
self.check('---\n' self.check('---\n'
'object:\n' 'object:\n'
' k1:\n' ' k1:\n'
@@ -605,7 +609,7 @@ class IndentationTestCase(RuleTestCase):
'...\n', conf) '...\n', conf)
def test_three_spaces(self): def test_three_spaces(self):
conf = 'indentation: {spaces: 3, indent-sequences: no}' conf = 'indentation: {spaces: 3, indent-sequences: false}'
self.check('---\n' self.check('---\n'
'object:\n' 'object:\n'
' k1:\n' ' k1:\n'
@@ -618,7 +622,7 @@ class IndentationTestCase(RuleTestCase):
' - name: Linux\n' ' - name: Linux\n'
' date: 1991\n' ' date: 1991\n'
'...\n', conf) '...\n', conf)
conf = 'indentation: {spaces: 3, indent-sequences: yes}' conf = 'indentation: {spaces: 3, indent-sequences: true}'
self.check('---\n' self.check('---\n'
'object:\n' 'object:\n'
' k1:\n' ' k1:\n'
@@ -632,7 +636,7 @@ class IndentationTestCase(RuleTestCase):
' date: 1991\n' ' date: 1991\n'
'...\n', conf) '...\n', conf)
def test_consistent(self): def test_consistent_spaces(self):
conf = ('indentation: {spaces: consistent,\n' conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: whatever}\n' ' indent-sequences: whatever}\n'
'document-start: disable\n') 'document-start: disable\n')
@@ -713,6 +717,142 @@ class IndentationTestCase(RuleTestCase):
'- b\n' '- b\n'
'- c\n', conf) '- c\n', conf)
def test_consistent_spaces_and_indent_sequences(self):
conf = 'indentation: {spaces: consistent, indent-sequences: true}'
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem1=(3, 1))
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem1=(7, 5))
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
'- a\n'
'- b\n'
'- c\n', conf, problem1=(7, 1))
conf = 'indentation: {spaces: consistent, indent-sequences: false}'
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem1=(7, 5))
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem1=(7, 3))
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
'- a\n'
'- b\n'
'- c\n', conf, problem1=(3, 3))
conf = ('indentation: {spaces: consistent,\n'
' indent-sequences: consistent}')
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem1=(7, 5))
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
'- a\n'
'- b\n'
'- c\n', conf, problem1=(7, 1))
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
'- a\n'
'- b\n'
'- c\n', conf)
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem1=(7, 5))
conf = 'indentation: {spaces: consistent, indent-sequences: whatever}'
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf)
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
'- a\n'
'- b\n'
'- c\n', conf)
self.check('---\n'
'list one:\n'
'- 1\n'
'- 2\n'
'- 3\n'
'list two:\n'
'- a\n'
'- b\n'
'- c\n', conf)
self.check('---\n'
'list one:\n'
' - 1\n'
' - 2\n'
' - 3\n'
'list two:\n'
' - a\n'
' - b\n'
' - c\n', conf, problem1=(7, 5))
def test_indent_sequences_whatever(self): def test_indent_sequences_whatever(self):
conf = 'indentation: {spaces: 4, indent-sequences: whatever}' conf = 'indentation: {spaces: 4, indent-sequences: whatever}'
self.check('---\n' self.check('---\n'
@@ -1072,6 +1212,20 @@ class IndentationTestCase(RuleTestCase):
' - name: Linux\n' ' - name: Linux\n'
' date: 1991\n' ' date: 1991\n'
'...\n', conf, problem=(5, 10, 'syntax')) '...\n', conf, problem=(5, 10, 'syntax'))
conf = 'indentation: {spaces: 2, indent-sequences: true}'
self.check('---\n'
'a:\n'
'-\n' # empty list
'b: c\n'
'...\n', conf, problem=(3, 1))
conf = 'indentation: {spaces: 2, indent-sequences: consistent}'
self.check('---\n'
'a:\n'
' -\n' # empty list
'b:\n'
'-\n'
'c: d\n'
'...\n', conf, problem=(5, 1))
def test_over_indented(self): def test_over_indented(self):
conf = 'indentation: {spaces: 2, indent-sequences: consistent}' conf = 'indentation: {spaces: 2, indent-sequences: consistent}'
@@ -1128,9 +1282,23 @@ class IndentationTestCase(RuleTestCase):
' - subel\n' ' - subel\n'
'...\n', conf, '...\n', conf,
problem=(2, 3)) problem=(2, 3))
conf = 'indentation: {spaces: 2, indent-sequences: false}'
self.check('---\n'
'a:\n'
' -\n' # empty list
'b: c\n'
'...\n', conf, problem=(3, 3))
conf = 'indentation: {spaces: 2, indent-sequences: consistent}'
self.check('---\n'
'a:\n'
'-\n' # empty list
'b:\n'
' -\n'
'c: d\n'
'...\n', conf, problem=(5, 3))
def test_multi_lines(self): def test_multi_lines(self):
conf = 'indentation: {spaces: consistent, indent-sequences: yes}' conf = 'indentation: {spaces: consistent, indent-sequences: true}'
self.check('---\n' self.check('---\n'
'long_string: >\n' 'long_string: >\n'
' bla bla blah\n' ' bla bla blah\n'
@@ -1438,7 +1606,7 @@ class IndentationTestCase(RuleTestCase):
'- !!map # Block collection\n' '- !!map # Block collection\n'
' foo: bar\n', conf) ' foo: bar\n', conf)
conf = 'indentation: {spaces: consistent, indent-sequences: no}' conf = 'indentation: {spaces: consistent, indent-sequences: false}'
self.check('---\n' self.check('---\n'
'sequence: !!seq\n' 'sequence: !!seq\n'
'- entry\n' '- entry\n'
@@ -1505,7 +1673,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_basics_plain(self): def test_basics_plain(self):
conf = ('indentation: {spaces: consistent,\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: no}\n' ' check-multi-line-strings: false}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('multi\n' self.check('multi\n'
'line\n', conf) 'line\n', conf)
@@ -1534,7 +1702,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_check_multi_line_plain(self): def test_check_multi_line_plain(self):
conf = ('indentation: {spaces: consistent,\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n' ' check-multi-line-strings: true}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('multi\n' self.check('multi\n'
' line\n', conf, problem=(2, 2)) ' line\n', conf, problem=(2, 2))
@@ -1557,7 +1725,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_basics_quoted(self): def test_basics_quoted(self):
conf = ('indentation: {spaces: consistent,\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: no}\n' ' check-multi-line-strings: false}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('"multi\n' self.check('"multi\n'
' line"\n', conf) ' line"\n', conf)
@@ -1588,7 +1756,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_check_multi_line_quoted(self): def test_check_multi_line_quoted(self):
conf = ('indentation: {spaces: consistent,\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n' ' check-multi-line-strings: true}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('"multi\n' self.check('"multi\n'
'line"\n', conf, problem=(2, 1)) 'line"\n', conf, problem=(2, 1))
@@ -1644,7 +1812,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_basics_folded_style(self): def test_basics_folded_style(self):
conf = ('indentation: {spaces: consistent,\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: no}\n' ' check-multi-line-strings: false}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('>\n' self.check('>\n'
' multi\n' ' multi\n'
@@ -1682,7 +1850,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_check_multi_line_folded_style(self): def test_check_multi_line_folded_style(self):
conf = ('indentation: {spaces: consistent,\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n' ' check-multi-line-strings: true}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('>\n' self.check('>\n'
' multi\n' ' multi\n'
@@ -1723,7 +1891,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_basics_literal_style(self): def test_basics_literal_style(self):
conf = ('indentation: {spaces: consistent,\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: no}\n' ' check-multi-line-strings: false}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('|\n' self.check('|\n'
' multi\n' ' multi\n'
@@ -1761,7 +1929,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_check_multi_line_literal_style(self): def test_check_multi_line_literal_style(self):
conf = ('indentation: {spaces: consistent,\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n' ' check-multi-line-strings: true}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('|\n' self.check('|\n'
' multi\n' ' multi\n'
@@ -1805,7 +1973,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_paragraph_plain(self): def test_paragraph_plain(self):
conf = ('indentation: {spaces: consistent,\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n' ' check-multi-line-strings: true}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('- long text: very "long"\n' self.check('- long text: very "long"\n'
' \'string\' with\n' ' \'string\' with\n'
@@ -1827,7 +1995,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_paragraph_double_quoted(self): def test_paragraph_double_quoted(self):
conf = ('indentation: {spaces: consistent,\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n' ' check-multi-line-strings: true}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('- long text: "very \\"long\\"\n' self.check('- long text: "very \\"long\\"\n'
' \'string\' with\n' ' \'string\' with\n'
@@ -1855,7 +2023,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_paragraph_single_quoted(self): def test_paragraph_single_quoted(self):
conf = ('indentation: {spaces: consistent,\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n' ' check-multi-line-strings: true}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('- long text: \'very "long"\n' self.check('- long text: \'very "long"\n'
' \'\'string\'\' with\n' ' \'\'string\'\' with\n'
@@ -1883,7 +2051,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_paragraph_folded(self): def test_paragraph_folded(self):
conf = ('indentation: {spaces: consistent,\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n' ' check-multi-line-strings: true}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('- long text: >\n' self.check('- long text: >\n'
' very "long"\n' ' very "long"\n'
@@ -1901,7 +2069,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_paragraph_literal(self): def test_paragraph_literal(self):
conf = ('indentation: {spaces: consistent,\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n' ' check-multi-line-strings: true}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('- long text: |\n' self.check('- long text: |\n'
' very "long"\n' ' very "long"\n'
@@ -1919,7 +2087,7 @@ class ScalarIndentationTestCase(RuleTestCase):
def test_consistent(self): def test_consistent(self):
conf = ('indentation: {spaces: consistent,\n' conf = ('indentation: {spaces: consistent,\n'
' check-multi-line-strings: yes}\n' ' check-multi-line-strings: true}\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('multi\n' self.check('multi\n'
'line\n', conf) 'line\n', conf)

View File

@@ -78,6 +78,15 @@ class KeyDuplicatesTestCase(RuleTestCase):
' duplicates with\n' ' duplicates with\n'
' many styles\n' ' many styles\n'
': 1\n', conf) ': 1\n', conf)
self.check('---\n'
'Merge Keys are OK:\n'
'anchor_one: &anchor_one\n'
' one: one\n'
'anchor_two: &anchor_two\n'
' two: two\n'
'anchor_reference:\n'
' <<: *anchor_one\n'
' <<: *anchor_two\n', conf)
def test_enabled(self): def test_enabled(self):
conf = 'key-duplicates: enable' conf = 'key-duplicates: enable'
@@ -147,6 +156,15 @@ class KeyDuplicatesTestCase(RuleTestCase):
': 1\n', conf, ': 1\n', conf,
problem1=(3, 1), problem2=(4, 1), problem3=(5, 3), problem1=(3, 1), problem2=(4, 1), problem3=(5, 3),
problem4=(7, 3)) problem4=(7, 3))
self.check('---\n'
'Merge Keys are OK:\n'
'anchor_one: &anchor_one\n'
' one: one\n'
'anchor_two: &anchor_two\n'
' two: two\n'
'anchor_reference:\n'
' <<: *anchor_one\n'
' <<: *anchor_two\n', conf)
def test_key_tokens_in_flow_sequences(self): def test_key_tokens_in_flow_sequences(self):
conf = 'key-duplicates: enable' conf = 'key-duplicates: enable'

View File

@@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Johannes F. Knauf
#
# 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 tests.common import RuleTestCase
class KeyOrderingTestCase(RuleTestCase):
rule_id = 'key-ordering'
def test_disabled(self):
conf = 'key-ordering: disable'
self.check('---\n'
'block mapping:\n'
' secondkey: a\n'
' firstkey: b\n', conf)
self.check('---\n'
'flow mapping:\n'
' {secondkey: a, firstkey: b}\n', conf)
self.check('---\n'
'second: before_first\n'
'at: root\n', conf)
self.check('---\n'
'nested but OK:\n'
' second: {first: 1}\n'
' third:\n'
' second: 2\n', conf)
def test_enabled(self):
conf = 'key-ordering: enable'
self.check('---\n'
'block mapping:\n'
' secondkey: a\n'
' firstkey: b\n', conf,
problem=(4, 3))
self.check('---\n'
'flow mapping:\n'
' {secondkey: a, firstkey: b}\n', conf,
problem=(3, 18))
self.check('---\n'
'second: before_first\n'
'at: root\n', conf,
problem=(3, 1))
self.check('---\n'
'nested but OK:\n'
' second: {first: 1}\n'
' third:\n'
' second: 2\n', conf)
def test_word_length(self):
conf = 'key-ordering: enable'
self.check('---\n'
'a: 1\n'
'ab: 1\n'
'abc: 1\n', conf)
self.check('---\n'
'a: 1\n'
'abc: 1\n'
'ab: 1\n', conf,
problem=(4, 1))
def test_key_duplicates(self):
conf = ('key-duplicates: disable\n'
'key-ordering: enable')
self.check('---\n'
'key: 1\n'
'key: 2\n', conf)
def test_case(self):
conf = 'key-ordering: enable'
self.check('---\n'
'T-shirt: 1\n'
'T-shirts: 2\n'
't-shirt: 3\n'
't-shirts: 4\n', conf)
self.check('---\n'
'T-shirt: 1\n'
't-shirt: 2\n'
'T-shirts: 3\n'
't-shirts: 4\n', conf,
problem=(4, 1))
def test_accents(self):
conf = 'key-ordering: enable'
self.check('---\n'
'hair: true\n'
'hais: true\n'
'haïr: true\n'
'haïssable: true\n', conf)
self.check('---\n'
'haïr: true\n'
'hais: true\n', conf,
problem=(3, 1))
self.check('---\n'
'haïr: true\n'
'hais: true\n', conf,
problem=(3, 1))
def test_key_tokens_in_flow_sequences(self):
conf = 'key-ordering: enable'
self.check('---\n'
'[\n'
' key: value, mappings, in, flow: sequence\n'
']\n', conf)

View File

@@ -14,6 +14,9 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import sys
import unittest
from tests.common import RuleTestCase from tests.common import RuleTestCase
@@ -66,7 +69,7 @@ class LineLengthTestCase(RuleTestCase):
self.check('---\n' + 81 * ' ' + '\n', conf, problem=(2, 81)) self.check('---\n' + 81 * ' ' + '\n', conf, problem=(2, 81))
def test_non_breakable_word(self): def test_non_breakable_word(self):
conf = 'line-length: {max: 20, allow-non-breakable-words: yes}' conf = 'line-length: {max: 20, allow-non-breakable-words: true}'
self.check('---\n' + 30 * 'A' + '\n', conf) self.check('---\n' + 30 * 'A' + '\n', conf)
self.check('---\n' self.check('---\n'
'this:\n' 'this:\n'
@@ -91,7 +94,7 @@ class LineLengthTestCase(RuleTestCase):
'long_line: http://localhost/very/very/long/url\n', conf, 'long_line: http://localhost/very/very/long/url\n', conf,
problem=(2, 21)) problem=(2, 21))
conf = 'line-length: {max: 20, allow-non-breakable-words: no}' conf = 'line-length: {max: 20, allow-non-breakable-words: false}'
self.check('---\n' + 30 * 'A' + '\n', conf, problem=(2, 21)) self.check('---\n' + 30 * 'A' + '\n', conf, problem=(2, 21))
self.check('---\n' self.check('---\n'
'this:\n' 'this:\n'
@@ -116,7 +119,7 @@ class LineLengthTestCase(RuleTestCase):
'long_line: http://localhost/very/very/long/url\n' 'long_line: http://localhost/very/very/long/url\n'
'...\n', conf, problem=(2, 21)) '...\n', conf, problem=(2, 21))
conf = ('line-length: {max: 20, allow-non-breakable-words: yes}\n' conf = ('line-length: {max: 20, allow-non-breakable-words: true}\n'
'trailing-spaces: disable') 'trailing-spaces: disable')
self.check('---\n' self.check('---\n'
'loooooooooong+word+and+some+space+at+the+end \n', 'loooooooooong+word+and+some+space+at+the+end \n',
@@ -124,7 +127,7 @@ class LineLengthTestCase(RuleTestCase):
def test_non_breakable_inline_mappings(self): def test_non_breakable_inline_mappings(self):
conf = 'line-length: {max: 20, ' \ conf = 'line-length: {max: 20, ' \
'allow-non-breakable-inline-mappings: yes}' 'allow-non-breakable-inline-mappings: true}'
self.check('---\n' self.check('---\n'
'long_line: http://localhost/very/very/long/url\n' 'long_line: http://localhost/very/very/long/url\n'
'long line: http://localhost/very/very/long/url\n', conf) 'long line: http://localhost/very/very/long/url\n', conf)
@@ -137,7 +140,7 @@ class LineLengthTestCase(RuleTestCase):
conf, problem1=(2, 21), problem2=(3, 21)) conf, problem1=(2, 21), problem2=(3, 21))
conf = ('line-length: {max: 20,' conf = ('line-length: {max: 20,'
' allow-non-breakable-inline-mappings: yes}\n' ' allow-non-breakable-inline-mappings: true}\n'
'trailing-spaces: disable') 'trailing-spaces: disable')
self.check('---\n' self.check('---\n'
'long_line: and+some+space+at+the+end \n', 'long_line: and+some+space+at+the+end \n',
@@ -150,8 +153,30 @@ class LineLengthTestCase(RuleTestCase):
conf, problem=(2, 21)) conf, problem=(2, 21))
# See https://github.com/adrienverge/yamllint/issues/21 # See https://github.com/adrienverge/yamllint/issues/21
conf = 'line-length: {allow-non-breakable-inline-mappings: yes}' conf = 'line-length: {allow-non-breakable-inline-mappings: true}'
self.check('---\n' self.check('---\n'
'content: |\n' 'content: |\n'
' {% this line is' + 99 * ' really' + ' long %}\n', ' {% this line is' + 99 * ' really' + ' long %}\n',
conf, problem=(3, 81)) conf, problem=(3, 81))
@unittest.skipIf(sys.version_info < (3, 0), 'Python 2 not supported')
def test_unicode(self):
conf = 'line-length: {max: 53}'
self.check('---\n'
'# This is a test to check if “line-length” works nice\n'
'with: “unicode characters” that span accross bytes! ↺\n',
conf)
conf = 'line-length: {max: 52}'
self.check('---\n'
'# This is a test to check if “line-length” works nice\n'
'with: “unicode characters” that span accross bytes! ↺\n',
conf, problem1=(2, 53), problem2=(3, 53))
def test_with_dos_newlines(self):
conf = ('line-length: {max: 10}\n'
'new-lines: {type: dos}\n'
'new-line-at-end-of-file: disable\n')
self.check('---\r\nABCD EFGHI', conf)
self.check('---\r\nABCD EFGHI\r\n', conf)
self.check('---\r\nABCD EFGHIJ', conf, problem=(2, 11))
self.check('---\r\nABCD EFGHIJ\r\n', conf, problem=(2, 11))

View File

@@ -31,16 +31,20 @@ class NewLinesTestCase(RuleTestCase):
self.check('---\r\ntext\r\n', conf) self.check('---\r\ntext\r\n', conf)
def test_unix_type(self): def test_unix_type(self):
conf = 'new-lines: {type: unix}' conf = ('new-line-at-end-of-file: disable\n'
'new-lines: {type: unix}\n')
self.check('', conf) self.check('', conf)
self.check('\r', conf)
self.check('\n', conf) self.check('\n', conf)
self.check('\r\n', conf, problem=(1, 1)) self.check('\r\n', conf, problem=(1, 1))
self.check('---\ntext\n', conf) self.check('---\ntext\n', conf)
self.check('---\r\ntext\r\n', conf, problem=(1, 4)) self.check('---\r\ntext\r\n', conf, problem=(1, 4))
def test_dos_type(self): def test_dos_type(self):
conf = 'new-lines: {type: dos}\n' conf = ('new-line-at-end-of-file: disable\n'
'new-lines: {type: dos}\n')
self.check('', conf) self.check('', conf)
self.check('\r', conf)
self.check('\n', conf, problem=(1, 1)) self.check('\n', conf, problem=(1, 1))
self.check('\r\n', conf) self.check('\r\n', conf)
self.check('---\ntext\n', conf, problem=(1, 4)) self.check('---\ntext\n', conf, problem=(1, 4))

View File

@@ -0,0 +1,76 @@
# -*- 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 tests.common import RuleTestCase
class OctalValuesTestCase(RuleTestCase):
rule_id = 'octal-values'
def test_disabled(self):
conf = ('octal-values: disable\n'
'new-line-at-end-of-file: disable\n'
'document-start: disable\n')
self.check('user-city: 010', conf)
self.check('user-city: 0o10', conf)
def test_implicit_octal_values(self):
conf = ('octal-values:\n'
' forbid-implicit-octal: true\n'
' forbid-explicit-octal: false\n'
'new-line-at-end-of-file: disable\n'
'document-start: disable\n')
self.check('user-city: 010', conf, problem=(1, 15))
self.check('user-city: abc', conf)
self.check('user-city: 010,0571', conf)
self.check("user-city: '010'", conf)
self.check('user-city: "010"', conf)
self.check('user-city:\n'
' - 010', conf, problem=(2, 8))
self.check('user-city: [010]', conf, problem=(1, 16))
self.check('user-city: {beijing: 010}', conf, problem=(1, 25))
self.check('explicit-octal: 0o10', conf)
self.check('not-number: 0abc', conf)
self.check('zero: 0', conf)
self.check('hex-value: 0x10', conf)
self.check('number-values:\n'
' - 0.10\n'
' - .01\n'
' - 0e3\n', conf)
def test_explicit_octal_values(self):
conf = ('octal-values:\n'
' forbid-implicit-octal: false\n'
' forbid-explicit-octal: true\n'
'new-line-at-end-of-file: disable\n'
'document-start: disable\n')
self.check('user-city: 0o10', conf, problem=(1, 16))
self.check('user-city: abc', conf)
self.check('user-city: 0o10,0571', conf)
self.check("user-city: '0o10'", conf)
self.check('user-city:\n'
' - 0o10', conf, problem=(2, 9))
self.check('user-city: [0o10]', conf, problem=(1, 17))
self.check('user-city: {beijing: 0o10}', conf, problem=(1, 26))
self.check('implicit-octal: 010', conf)
self.check('not-number: 0oabc', conf)
self.check('zero: 0', conf)
self.check('hex-value: 0x10', conf)
self.check('number-values:\n'
' - 0.10\n'
' - .01\n'
' - 0e3\n', conf)
self.check('user-city: "010"', conf)

View File

@@ -0,0 +1,437 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 ClearScore
#
# 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 tests.common import RuleTestCase
from yamllint import config
class QuotedTestCase(RuleTestCase):
rule_id = 'quoted-strings'
def test_disabled(self):
conf = 'quoted-strings: disable'
self.check('---\n'
'foo: bar\n', conf)
self.check('---\n'
'foo: "bar"\n', conf)
self.check('---\n'
'foo: \'bar\'\n', conf)
self.check('---\n'
'bar: 123\n', conf)
self.check('---\n'
'bar: "123"\n', conf)
def test_quote_type_any(self):
conf = 'quoted-strings: {quote-type: any}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n' # fails
'string2: "foo"\n'
'string3: "true"\n'
'string4: "123"\n'
'string5: \'bar\'\n'
'string6: !!str genericstring\n'
'string7: !!str 456\n'
'string8: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n'
'block-seq:\n'
' - foo\n' # fails
' - "foo"\n'
'flow-seq: [foo, "foo"]\n' # fails
'flow-map: {a: foo, b: "foo"}\n', # fails
conf, problem1=(4, 10), problem2=(17, 5),
problem3=(19, 12), problem4=(20, 15))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n' # fails
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n'
' word 2"\n',
conf, problem1=(9, 3))
def test_quote_type_single(self):
conf = 'quoted-strings: {quote-type: single}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n' # fails
'string2: "foo"\n' # fails
'string3: "true"\n' # fails
'string4: "123"\n' # fails
'string5: \'bar\'\n'
'string6: !!str genericstring\n'
'string7: !!str 456\n'
'string8: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n'
'block-seq:\n'
' - foo\n' # fails
' - "foo"\n' # fails
'flow-seq: [foo, "foo"]\n' # fails
'flow-map: {a: foo, b: "foo"}\n', # fails
conf, problem1=(4, 10), problem2=(5, 10), problem3=(6, 10),
problem4=(7, 10), problem5=(17, 5), problem6=(18, 5),
problem7=(19, 12), problem8=(19, 17), problem9=(20, 15),
problem10=(20, 23))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n' # fails
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n'
' word 2"\n',
conf, problem1=(9, 3), problem2=(12, 3))
def test_quote_type_double(self):
conf = 'quoted-strings: {quote-type: double}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n' # fails
'string2: "foo"\n'
'string3: "true"\n'
'string4: "123"\n'
'string5: \'bar\'\n' # fails
'string6: !!str genericstring\n'
'string7: !!str 456\n'
'string8: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n'
'block-seq:\n'
' - foo\n' # fails
' - "foo"\n'
'flow-seq: [foo, "foo"]\n' # fails
'flow-map: {a: foo, b: "foo"}\n', # fails
conf, problem1=(4, 10), problem2=(8, 10), problem3=(17, 5),
problem4=(19, 12), problem5=(20, 15))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n' # fails
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n'
' word 2"\n',
conf, problem1=(9, 3))
def test_any_quotes_not_required(self):
conf = 'quoted-strings: {quote-type: any, required: false}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n'
'string2: "foo"\n'
'string3: "true"\n'
'string4: "123"\n'
'string5: \'bar\'\n'
'string6: !!str genericstring\n'
'string7: !!str 456\n'
'string8: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n'
'block-seq:\n'
' - foo\n' # fails
' - "foo"\n'
'flow-seq: [foo, "foo"]\n' # fails
'flow-map: {a: foo, b: "foo"}\n', # fails
conf)
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n'
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n'
' word 2"\n',
conf)
def test_single_quotes_not_required(self):
conf = 'quoted-strings: {quote-type: single, required: false}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n'
'string2: "foo"\n' # fails
'string3: "true"\n' # fails
'string4: "123"\n' # fails
'string5: \'bar\'\n'
'string6: !!str genericstring\n'
'string7: !!str 456\n'
'string8: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n'
'block-seq:\n'
' - foo\n' # fails
' - "foo"\n'
'flow-seq: [foo, "foo"]\n' # fails
'flow-map: {a: foo, b: "foo"}\n', # fails
conf, problem1=(5, 10), problem2=(6, 10), problem3=(7, 10),
problem4=(18, 5), problem5=(19, 17), problem6=(20, 23))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n'
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n' # fails
' word 2"\n',
conf, problem1=(12, 3))
def test_only_when_needed(self):
conf = 'quoted-strings: {required: only-when-needed}\n'
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n'
'string2: "foo"\n' # fails
'string3: "true"\n'
'string4: "123"\n'
'string5: \'bar\'\n' # fails
'string6: !!str genericstring\n'
'string7: !!str 456\n'
'string8: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n'
'block-seq:\n'
' - foo\n'
' - "foo"\n' # fails
'flow-seq: [foo, "foo"]\n' # fails
'flow-map: {a: foo, b: "foo"}\n', # fails
conf, problem1=(5, 10), problem2=(8, 10), problem3=(18, 5),
problem4=(19, 17), problem5=(20, 23))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n'
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n' # fails
' word 2"\n',
conf, problem1=(12, 3))
def test_only_when_needed_single_quotes(self):
conf = ('quoted-strings: {quote-type: single,\n'
' required: only-when-needed}\n')
self.check('---\n'
'boolean1: true\n'
'number1: 123\n'
'string1: foo\n'
'string2: "foo"\n' # fails
'string3: "true"\n' # fails
'string4: "123"\n' # fails
'string5: \'bar\'\n' # fails
'string6: !!str genericstring\n'
'string7: !!str 456\n'
'string8: !!str "quotedgenericstring"\n'
'binary: !!binary binstring\n'
'integer: !!int intstring\n'
'boolean2: !!bool boolstring\n'
'boolean3: !!bool "quotedboolstring"\n'
'block-seq:\n'
' - foo\n'
' - "foo"\n' # fails
'flow-seq: [foo, "foo"]\n' # fails
'flow-map: {a: foo, b: "foo"}\n', # fails
conf, problem1=(5, 10), problem2=(6, 10), problem3=(7, 10),
problem4=(8, 10), problem5=(18, 5), problem6=(19, 17),
problem7=(20, 23))
self.check('---\n'
'multiline string 1: |\n'
' line 1\n'
' line 2\n'
'multiline string 2: >\n'
' word 1\n'
' word 2\n'
'multiline string 3:\n'
' word 1\n'
' word 2\n'
'multiline string 4:\n'
' "word 1\\\n' # fails
' word 2"\n',
conf, problem1=(12, 3))
def test_only_when_needed_corner_cases(self):
conf = 'quoted-strings: {required: only-when-needed}\n'
self.check('---\n'
'- ""\n'
'- "- item"\n'
'- "key: value"\n'
'- "%H:%M:%S"\n'
'- "%wheel ALL=(ALL) NOPASSWD: ALL"\n'
'- \'"quoted"\'\n'
'- "\'foo\' == \'bar\'"\n'
'- "\'Mac\' in ansible_facts.product_name"\n',
conf)
self.check('---\n'
'k1: ""\n'
'k2: "- item"\n'
'k3: "key: value"\n'
'k4: "%H:%M:%S"\n'
'k5: "%wheel ALL=(ALL) NOPASSWD: ALL"\n'
'k6: \'"quoted"\'\n'
'k7: "\'foo\' == \'bar\'"\n'
'k8: "\'Mac\' in ansible_facts.product_name"\n',
conf)
self.check('---\n'
'- ---\n'
'- "---"\n' # fails
'- ----------\n'
'- "----------"\n' # fails
'- :wq\n'
'- ":wq"\n', # fails
conf, problem1=(3, 3), problem2=(5, 3), problem3=(7, 3))
self.check('---\n'
'k1: ---\n'
'k2: "---"\n' # fails
'k3: ----------\n'
'k4: "----------"\n' # fails
'k5: :wq\n'
'k6: ":wq"\n', # fails
conf, problem1=(3, 5), problem2=(5, 5), problem3=(7, 5))
def test_only_when_needed_extras(self):
conf = ('quoted-strings:\n'
' required: true\n'
' extra-allowed: [^http://]\n')
self.assertRaises(config.YamlLintConfigError, self.check, '', conf)
conf = ('quoted-strings:\n'
' required: true\n'
' extra-required: [^http://]\n')
self.assertRaises(config.YamlLintConfigError, self.check, '', conf)
conf = ('quoted-strings:\n'
' required: false\n'
' extra-allowed: [^http://]\n')
self.assertRaises(config.YamlLintConfigError, self.check, '', conf)
conf = ('quoted-strings:\n'
' required: true\n')
self.check('---\n'
'- 123\n'
'- "123"\n'
'- localhost\n' # fails
'- "localhost"\n'
'- http://localhost\n' # fails
'- "http://localhost"\n'
'- ftp://localhost\n' # fails
'- "ftp://localhost"\n',
conf, problem1=(4, 3), problem2=(6, 3), problem3=(8, 3))
conf = ('quoted-strings:\n'
' required: only-when-needed\n'
' extra-allowed: [^ftp://]\n'
' extra-required: [^http://]\n')
self.check('---\n'
'- 123\n'
'- "123"\n'
'- localhost\n'
'- "localhost"\n' # fails
'- http://localhost\n' # fails
'- "http://localhost"\n'
'- ftp://localhost\n'
'- "ftp://localhost"\n',
conf, problem1=(5, 3), problem2=(6, 3))
conf = ('quoted-strings:\n'
' required: false\n'
' extra-required: [^http://, ^ftp://]\n')
self.check('---\n'
'- 123\n'
'- "123"\n'
'- localhost\n'
'- "localhost"\n'
'- http://localhost\n' # fails
'- "http://localhost"\n'
'- ftp://localhost\n' # fails
'- "ftp://localhost"\n',
conf, problem1=(6, 3), problem2=(8, 3))
conf = ('quoted-strings:\n'
' required: only-when-needed\n'
' extra-allowed: [^ftp://, ";$", " "]\n')
self.check('---\n'
'- localhost\n'
'- "localhost"\n' # fails
'- ftp://localhost\n'
'- "ftp://localhost"\n'
'- i=i+1\n'
'- "i=i+1"\n' # fails
'- i=i+2;\n'
'- "i=i+2;"\n'
'- foo\n'
'- "foo"\n' # fails
'- foo bar\n'
'- "foo bar"\n',
conf, problem1=(3, 3), problem2=(7, 3), problem3=(11, 3))

View File

@@ -49,6 +49,54 @@ class TruthyTestCase(RuleTestCase):
problem3=(7, 3), problem4=(7, 7), problem3=(7, 3), problem4=(7, 7),
problem5=(8, 3), problem6=(8, 7)) problem5=(8, 3), problem6=(8, 7))
def test_different_allowed_values(self):
conf = ('truthy:\n'
' allowed-values: ["yes", "no"]\n')
self.check('---\n'
'key1: foo\n'
'key2: yes\n'
'key3: bar\n'
'key4: no\n', conf)
self.check('---\n'
'key1: true\n'
'key2: Yes\n'
'key3: false\n'
'key4: no\n'
'key5: yes\n',
conf,
problem1=(2, 7), problem2=(3, 7),
problem3=(4, 7))
def test_combined_allowed_values(self):
conf = ('truthy:\n'
' allowed-values: ["yes", "no", "true", "false"]\n')
self.check('---\n'
'key1: foo\n'
'key2: yes\n'
'key3: bar\n'
'key4: no\n', conf)
self.check('---\n'
'key1: true\n'
'key2: Yes\n'
'key3: false\n'
'key4: no\n'
'key5: yes\n',
conf, problem1=(3, 7))
def test_no_allowed_values(self):
conf = ('truthy:\n'
' allowed-values: []\n')
self.check('---\n'
'key1: foo\n'
'key2: bar\n', conf)
self.check('---\n'
'key1: true\n'
'key2: yes\n'
'key3: false\n'
'key4: no\n', conf,
problem1=(2, 7), problem2=(3, 7),
problem3=(4, 7), problem4=(5, 7))
def test_explicit_types(self): def test_explicit_types(self):
conf = 'truthy: enable\n' conf = 'truthy: enable\n'
self.check('---\n' self.check('---\n'
@@ -66,3 +114,33 @@ class TruthyTestCase(RuleTestCase):
'boolean5: !!bool off\n' 'boolean5: !!bool off\n'
'boolean6: !!bool NO\n', 'boolean6: !!bool NO\n',
conf) conf)
def test_check_keys_disabled(self):
conf = ('truthy:\n'
' allowed-values: []\n'
' check-keys: false\n'
'key-duplicates: disable\n')
self.check('---\n'
'YES: 0\n'
'Yes: 0\n'
'yes: 0\n'
'No: 0\n'
'No: 0\n'
'no: 0\n'
'TRUE: 0\n'
'True: 0\n'
'true: 0\n'
'FALSE: 0\n'
'False: 0\n'
'false: 0\n'
'ON: 0\n'
'On: 0\n'
'on: 0\n'
'OFF: 0\n'
'Off: 0\n'
'off: 0\n'
'YES:\n'
' Yes:\n'
' yes:\n'
' on: 0\n',
conf)

View File

@@ -23,82 +23,105 @@ import locale
import os import os
import pty import pty
import shutil import shutil
import tempfile
import unittest
import sys import sys
import unittest
from tests.common import build_temp_workspace
from yamllint import cli from yamllint import cli
from yamllint import config
class RunContext(object):
"""Context manager for ``cli.run()`` to capture exit code and streams."""
def __init__(self, case):
self.stdout = self.stderr = None
self._raises_ctx = case.assertRaises(SystemExit)
def __enter__(self):
self._raises_ctx.__enter__()
sys.stdout = self.outstream = StringIO()
sys.stderr = self.errstream = StringIO()
return self
def __exit__(self, *exc_info):
self.stdout, sys.stdout = self.outstream.getvalue(), sys.__stdout__
self.stderr, sys.stderr = self.errstream.getvalue(), sys.__stderr__
return self._raises_ctx.__exit__(*exc_info)
@property
def returncode(self):
return self._raises_ctx.exception.code
class CommandLineTestCase(unittest.TestCase): class CommandLineTestCase(unittest.TestCase):
def setUp(self): @classmethod
self.wd = tempfile.mkdtemp(prefix='yamllint-tests-') def setUpClass(cls):
super(CommandLineTestCase, cls).setUpClass()
# .yaml file at root cls.wd = build_temp_workspace({
with open(os.path.join(self.wd, 'a.yaml'), 'w') as f: # .yaml file at root
f.write('---\n' 'a.yaml': '---\n'
'- 1 \n' '- 1 \n'
'- 2') '- 2',
# file with only one warning
'warn.yaml': 'key: value\n',
# .yml file at root
'empty.yml': '',
# file in dir
'sub/ok.yaml': '---\n'
'key: value\n',
# file in very nested dir
's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml': '---\n'
'key: value\n'
'key: other value\n',
# empty dir
'empty-dir': [],
# non-YAML file
'no-yaml.json': '---\n'
'key: value\n',
# non-ASCII chars
'non-ascii/éçäγλνπ¥/utf-8': (
u'---\n'
u'- hétérogénéité\n'
u'# 19.99 €\n'
u'- お早う御座います。\n'
u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'),
# dos line endings yaml
'dos.yml': '---\r\n'
'dos: true',
})
# .yml file at root @classmethod
open(os.path.join(self.wd, 'empty.yml'), 'w').close() def tearDownClass(cls):
super(CommandLineTestCase, cls).tearDownClass()
# file in dir shutil.rmtree(cls.wd)
os.mkdir(os.path.join(self.wd, 'sub'))
with open(os.path.join(self.wd, 'sub', 'ok.yaml'), 'w') as f:
f.write('---\n'
'key: value\n')
# file in very nested dir
dir = self.wd
for i in range(15):
dir = os.path.join(dir, 's')
os.mkdir(dir)
with open(os.path.join(dir, 'file.yaml'), 'w') as f:
f.write('---\n'
'key: value\n'
'key: other value\n')
# empty dir
os.mkdir(os.path.join(self.wd, 'empty-dir'))
# non-YAML file
with open(os.path.join(self.wd, 'no-yaml.json'), 'w') as f:
f.write('---\n'
'key: value\n')
# non-ASCII chars
os.mkdir(os.path.join(self.wd, 'non-ascii'))
with open(os.path.join(self.wd, 'non-ascii', 'utf-8'), 'wb') as f:
f.write((u'---\n'
u'- hétérogénéité\n'
u'# 19.99 €\n'
u'- お早う御座います。\n'
u'# الأَبْجَدِيَّة العَرَبِيَّة\n').encode('utf-8'))
def tearDown(self):
shutil.rmtree(self.wd)
def test_find_files_recursively(self): def test_find_files_recursively(self):
conf = config.YamlLintConfig('extends: default')
self.assertEqual( self.assertEqual(
sorted(cli.find_files_recursively([self.wd])), sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'a.yaml'), [os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'dos.yml'),
os.path.join(self.wd, 'empty.yml'), os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'), os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/ok.yaml')], os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')],
) )
items = [os.path.join(self.wd, 'sub/ok.yaml'), items = [os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'empty-dir')] os.path.join(self.wd, 'empty-dir')]
self.assertEqual( self.assertEqual(
sorted(cli.find_files_recursively(items)), sorted(cli.find_files_recursively(items, conf)),
[os.path.join(self.wd, 'sub/ok.yaml')], [os.path.join(self.wd, 'sub/ok.yaml')],
) )
items = [os.path.join(self.wd, 'empty.yml'), items = [os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 's')] os.path.join(self.wd, 's')]
self.assertEqual( self.assertEqual(
sorted(cli.find_files_recursively(items)), sorted(cli.find_files_recursively(items, conf)),
[os.path.join(self.wd, 'empty.yml'), [os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml')], os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml')],
) )
@@ -106,237 +129,267 @@ class CommandLineTestCase(unittest.TestCase):
items = [os.path.join(self.wd, 'sub'), items = [os.path.join(self.wd, 'sub'),
os.path.join(self.wd, '/etc/another/file')] os.path.join(self.wd, '/etc/another/file')]
self.assertEqual( self.assertEqual(
sorted(cli.find_files_recursively(items)), sorted(cli.find_files_recursively(items, conf)),
[os.path.join(self.wd, '/etc/another/file'), [os.path.join(self.wd, '/etc/another/file'),
os.path.join(self.wd, 'sub/ok.yaml')], os.path.join(self.wd, 'sub/ok.yaml')],
) )
conf = config.YamlLintConfig('extends: default\n'
'yaml-files:\n'
' - \'*.yaml\' \n')
self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
conf = config.YamlLintConfig('extends: default\n'
'yaml-files:\n'
' - \'*.yml\'\n')
self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'dos.yml'),
os.path.join(self.wd, 'empty.yml')]
)
conf = config.YamlLintConfig('extends: default\n'
'yaml-files:\n'
' - \'*.json\'\n')
self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'no-yaml.json')]
)
conf = config.YamlLintConfig('extends: default\n'
'yaml-files:\n'
' - \'*\'\n')
self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'dos.yml'),
os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 'no-yaml.json'),
os.path.join(self.wd, 'non-ascii/éçäγλνπ¥/utf-8'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
conf = config.YamlLintConfig('extends: default\n'
'yaml-files:\n'
' - \'*.yaml\'\n'
' - \'*\'\n'
' - \'**\'\n')
self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'a.yaml'),
os.path.join(self.wd, 'dos.yml'),
os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 'no-yaml.json'),
os.path.join(self.wd, 'non-ascii/éçäγλνπ¥/utf-8'),
os.path.join(self.wd, 's/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'),
os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')]
)
conf = config.YamlLintConfig('extends: default\n'
'yaml-files:\n'
' - \'s/**\'\n'
' - \'**/utf-8\'\n')
self.assertEqual(
sorted(cli.find_files_recursively([self.wd], conf)),
[os.path.join(self.wd, 'non-ascii/éçäγλνπ¥/utf-8')]
)
def test_run_with_bad_arguments(self): def test_run_with_bad_arguments(self):
sys.stdout, sys.stderr = StringIO(), StringIO() with RunContext(self) as ctx:
with self.assertRaises(SystemExit) as ctx:
cli.run(()) cli.run(())
self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '')
self.assertRegexpMatches(ctx.stderr, r'^usage')
self.assertNotEqual(ctx.exception.code, 0) with RunContext(self) as ctx:
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'^usage')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('--unknown-arg', )) cli.run(('--unknown-arg', ))
self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '')
self.assertRegexpMatches(ctx.stderr, r'^usage')
self.assertNotEqual(ctx.exception.code, 0) with RunContext(self) as ctx:
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'^usage')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-c', './conf.yaml', '-d', 'relaxed', 'file')) cli.run(('-c', './conf.yaml', '-d', 'relaxed', 'file'))
self.assertNotEqual(ctx.returncode, 0)
self.assertEqual(ctx.stdout, '')
self.assertRegexpMatches(
ctx.stderr.splitlines()[-1],
r'^yamllint: error: argument -d\/--config-data: '
r'not allowed with argument -c\/--config-file$'
)
self.assertNotEqual(ctx.exception.code, 0) # checks if reading from stdin and files are mutually exclusive
with RunContext(self) as ctx:
out, err = sys.stdout.getvalue(), sys.stderr.getvalue() cli.run(('-', 'file'))
self.assertEqual(out, '') self.assertNotEqual(ctx.returncode, 0)
self.assertRegexpMatches(err, r'^Options --config-file and ' self.assertEqual(ctx.stdout, '')
r'--config-data cannot be used') self.assertRegexpMatches(ctx.stderr, r'^usage')
def test_run_with_bad_config(self): def test_run_with_bad_config(self):
sys.stdout, sys.stderr = StringIO(), StringIO() with RunContext(self) as ctx:
with self.assertRaises(SystemExit) as ctx:
cli.run(('-d', 'rules: {a: b}', 'file')) cli.run(('-d', 'rules: {a: b}', 'file'))
self.assertEqual(ctx.returncode, -1)
self.assertEqual(ctx.exception.code, -1) self.assertEqual(ctx.stdout, '')
self.assertRegexpMatches(ctx.stderr, r'^invalid config: no such rule')
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'^invalid config: no such rule')
def test_run_with_empty_config(self): def test_run_with_empty_config(self):
sys.stdout, sys.stderr = StringIO(), StringIO() with RunContext(self) as ctx:
with self.assertRaises(SystemExit) as ctx:
cli.run(('-d', '', 'file')) cli.run(('-d', '', 'file'))
self.assertEqual(ctx.returncode, -1)
self.assertEqual(ctx.exception.code, -1) self.assertEqual(ctx.stdout, '')
self.assertRegexpMatches(ctx.stderr, r'^invalid config: not a dict')
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'^invalid config: not a dict')
def test_run_with_config_file(self): def test_run_with_config_file(self):
with open(os.path.join(self.wd, 'config'), 'w') as f: with open(os.path.join(self.wd, 'config'), 'w') as f:
f.write('rules: {trailing-spaces: disable}') f.write('rules: {trailing-spaces: disable}')
with self.assertRaises(SystemExit) as ctx: with RunContext(self) as ctx:
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml'))) cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
self.assertEqual(ctx.exception.code, 0) self.assertEqual(ctx.returncode, 0)
with open(os.path.join(self.wd, 'config'), 'w') as f: with open(os.path.join(self.wd, 'config'), 'w') as f:
f.write('rules: {trailing-spaces: enable}') f.write('rules: {trailing-spaces: enable}')
with self.assertRaises(SystemExit) as ctx: with RunContext(self) as ctx:
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml'))) cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
self.assertEqual(ctx.exception.code, 1) self.assertEqual(ctx.returncode, 1)
def test_run_with_user_global_config_file(self): def test_run_with_user_global_config_file(self):
home = os.path.join(self.wd, 'fake-home') home = os.path.join(self.wd, 'fake-home')
os.mkdir(home) dir = os.path.join(home, '.config', 'yamllint')
dir = os.path.join(home, '.config') os.makedirs(dir)
os.mkdir(dir)
dir = os.path.join(dir, 'yamllint')
os.mkdir(dir)
config = os.path.join(dir, 'config') config = os.path.join(dir, 'config')
temp = os.environ['HOME'] self.addCleanup(os.environ.update, HOME=os.environ['HOME'])
os.environ['HOME'] = home os.environ['HOME'] = home
with open(config, 'w') as f: with open(config, 'w') as f:
f.write('rules: {trailing-spaces: disable}') f.write('rules: {trailing-spaces: disable}')
with self.assertRaises(SystemExit) as ctx: with RunContext(self) as ctx:
cli.run((os.path.join(self.wd, 'a.yaml'), )) cli.run((os.path.join(self.wd, 'a.yaml'), ))
self.assertEqual(ctx.exception.code, 0) self.assertEqual(ctx.returncode, 0)
with open(config, 'w') as f: with open(config, 'w') as f:
f.write('rules: {trailing-spaces: enable}') f.write('rules: {trailing-spaces: enable}')
with self.assertRaises(SystemExit) as ctx: with RunContext(self) as ctx:
cli.run((os.path.join(self.wd, 'a.yaml'), )) cli.run((os.path.join(self.wd, 'a.yaml'), ))
self.assertEqual(ctx.exception.code, 1) self.assertEqual(ctx.returncode, 1)
os.environ['HOME'] = temp
def test_run_version(self): def test_run_version(self):
sys.stdout, sys.stderr = StringIO(), StringIO() with RunContext(self) as ctx:
with self.assertRaises(SystemExit) as ctx:
cli.run(('--version', )) cli.run(('--version', ))
self.assertEqual(ctx.returncode, 0)
self.assertEqual(ctx.exception.code, 0) self.assertRegexpMatches(ctx.stdout + ctx.stderr, r'yamllint \d+\.\d+')
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertRegexpMatches(out + err, r'yamllint \d+\.\d+')
def test_run_non_existing_file(self): def test_run_non_existing_file(self):
file = os.path.join(self.wd, 'i-do-not-exist.yaml') path = os.path.join(self.wd, 'i-do-not-exist.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO() with RunContext(self) as ctx:
with self.assertRaises(SystemExit) as ctx: cli.run(('-f', 'parsable', path))
cli.run(('-f', 'parsable', file)) self.assertEqual(ctx.returncode, -1)
self.assertEqual(ctx.stdout, '')
self.assertEqual(ctx.exception.code, -1) self.assertRegexpMatches(ctx.stderr, r'No such file or directory')
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(err, r'No such file or directory')
def test_run_one_problem_file(self): def test_run_one_problem_file(self):
file = os.path.join(self.wd, 'a.yaml') path = os.path.join(self.wd, 'a.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO() with RunContext(self) as ctx:
with self.assertRaises(SystemExit) as ctx: cli.run(('-f', 'parsable', path))
cli.run(('-f', 'parsable', file)) self.assertEqual(ctx.returncode, 1)
self.assertEqual(ctx.stdout, (
self.assertEqual(ctx.exception.code, 1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, (
'%s:2:4: [error] trailing spaces (trailing-spaces)\n' '%s:2:4: [error] trailing spaces (trailing-spaces)\n'
'%s:3:4: [error] no new line character at the end of file ' '%s:3:4: [error] no new line character at the end of file '
'(new-line-at-end-of-file)\n') % (file, file)) '(new-line-at-end-of-file)\n' % (path, path)))
self.assertEqual(err, '') self.assertEqual(ctx.stderr, '')
def test_run_one_warning(self):
path = os.path.join(self.wd, 'warn.yaml')
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', path))
self.assertEqual(ctx.returncode, 0)
def test_run_warning_in_strict_mode(self):
path = os.path.join(self.wd, 'warn.yaml')
with RunContext(self) as ctx:
cli.run(('-f', 'parsable', '--strict', path))
self.assertEqual(ctx.returncode, 2)
def test_run_one_ok_file(self): def test_run_one_ok_file(self):
file = os.path.join(self.wd, 'sub', 'ok.yaml') path = os.path.join(self.wd, 'sub', 'ok.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO() with RunContext(self) as ctx:
with self.assertRaises(SystemExit) as ctx: cli.run(('-f', 'parsable', path))
cli.run(('-f', 'parsable', file)) self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
self.assertEqual(ctx.exception.code, 0)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertEqual(err, '')
def test_run_empty_file(self): def test_run_empty_file(self):
file = os.path.join(self.wd, 'empty.yml') path = os.path.join(self.wd, 'empty.yml')
sys.stdout, sys.stderr = StringIO(), StringIO() with RunContext(self) as ctx:
with self.assertRaises(SystemExit) as ctx: cli.run(('-f', 'parsable', path))
cli.run(('-f', 'parsable', file)) self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
self.assertEqual(ctx.exception.code, 0)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertEqual(err, '')
def test_run_non_ascii_file(self): def test_run_non_ascii_file(self):
file = os.path.join(self.wd, 'non-ascii', 'utf-8') path = os.path.join(self.wd, 'non-ascii', 'éçäγλνπ¥', 'utf-8')
# Make sure the default localization conditions on this "system" # Make sure the default localization conditions on this "system"
# support UTF-8 encoding. # support UTF-8 encoding.
loc = locale.getlocale() loc = locale.getlocale()
locale.setlocale(locale.LC_ALL, 'C.UTF-8') try:
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
except locale.Error:
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
self.addCleanup(locale.setlocale, locale.LC_ALL, loc)
sys.stdout, sys.stderr = StringIO(), StringIO() with RunContext(self) as ctx:
with self.assertRaises(SystemExit) as ctx: cli.run(('-f', 'parsable', path))
cli.run(('-f', 'parsable', file)) self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
locale.setlocale(locale.LC_ALL, loc)
self.assertEqual(ctx.exception.code, 0)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertEqual(err, '')
def test_run_multiple_files(self): def test_run_multiple_files(self):
items = [os.path.join(self.wd, 'empty.yml'), items = [os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 's')] os.path.join(self.wd, 's')]
file = items[1] + '/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml' path = items[1] + '/s/s/s/s/s/s/s/s/s/s/s/s/s/s/file.yaml'
sys.stdout, sys.stderr = StringIO(), StringIO() with RunContext(self) as ctx:
with self.assertRaises(SystemExit) as ctx:
cli.run(['-f', 'parsable'] + items) cli.run(['-f', 'parsable'] + items)
self.assertEqual((ctx.returncode, ctx.stderr), (1, ''))
self.assertEqual(ctx.exception.code, 1) self.assertEqual(ctx.stdout, (
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, (
'%s:3:1: [error] duplication of key "key" in mapping ' '%s:3:1: [error] duplication of key "key" in mapping '
'(key-duplicates)\n') % file) '(key-duplicates)\n') % path)
self.assertEqual(err, '')
def test_run_piped_output_nocolor(self): def test_run_piped_output_nocolor(self):
file = os.path.join(self.wd, 'a.yaml') path = os.path.join(self.wd, 'a.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO() with RunContext(self) as ctx:
with self.assertRaises(SystemExit) as ctx: cli.run((path, ))
cli.run((file, )) self.assertEqual((ctx.returncode, ctx.stderr), (1, ''))
self.assertEqual(ctx.stdout, (
self.assertEqual(ctx.exception.code, 1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, (
'%s\n' '%s\n'
' 2:4 error trailing spaces (trailing-spaces)\n' ' 2:4 error trailing spaces (trailing-spaces)\n'
' 3:4 error no new line character at the end of file ' ' 3:4 error no new line character at the end of file '
'(new-line-at-end-of-file)\n' '(new-line-at-end-of-file)\n'
'\n' % file)) '\n' % path))
self.assertEqual(err, '')
def test_run_colored_output(self): def test_run_default_format_output_in_tty(self):
file = os.path.join(self.wd, 'a.yaml') path = os.path.join(self.wd, 'a.yaml')
# Create a pseudo-TTY and redirect stdout to it # Create a pseudo-TTY and redirect stdout to it
master, slave = pty.openpty() master, slave = pty.openpty()
sys.stdout = sys.stderr = os.fdopen(slave, 'w') sys.stdout = sys.stderr = os.fdopen(slave, 'w')
with self.assertRaises(SystemExit) as ctx: with self.assertRaises(SystemExit) as ctx:
cli.run((file, )) cli.run((path, ))
sys.stdout.flush() sys.stdout.flush()
self.assertEqual(ctx.exception.code, 1) self.assertEqual(ctx.exception.code, 1)
@@ -359,4 +412,108 @@ class CommandLineTestCase(unittest.TestCase):
' \033[2m3:4\033[0m \033[31merror\033[0m ' ' \033[2m3:4\033[0m \033[31merror\033[0m '
'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' % path))
def test_run_default_format_output_without_tty(self):
path = os.path.join(self.wd, 'a.yaml')
with RunContext(self) as ctx:
cli.run((path, ))
expected_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' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_auto_output_without_tty_output(self):
path = os.path.join(self.wd, 'a.yaml')
with RunContext(self) as ctx:
cli.run((path, '--format', 'auto'))
expected_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' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_format_colored(self):
path = os.path.join(self.wd, 'a.yaml')
with RunContext(self) as ctx:
cli.run((path, '--format', 'colored'))
expected_out = (
'\033[4m%s\033[0m\n'
' \033[2m2:4\033[0m \033[31merror\033[0m '
'trailing spaces \033[2m(trailing-spaces)\033[0m\n'
' \033[2m3:4\033[0m \033[31merror\033[0m '
'no new line character at the end of file '
'\033[2m(new-line-at-end-of-file)\033[0m\n'
'\n' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
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
self.addCleanup(setattr, sys, 'stdin', sys.__stdin__)
sys.stdin = StringIO(
'I am a string\n'
'therefore: I am an error\n')
with RunContext(self) as ctx:
cli.run(('-', '-f', 'parsable'))
expected_out = (
'stdin:2:10: [error] syntax error: '
'mapping values are not allowed here (syntax)\n')
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
def test_run_no_warnings(self):
path = os.path.join(self.wd, 'a.yaml')
with RunContext(self) as ctx:
cli.run((path, '--no-warnings', '-f', 'auto'))
expected_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' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))
path = os.path.join(self.wd, 'warn.yaml')
with RunContext(self) as ctx:
cli.run((path, '--no-warnings', '-f', 'auto'))
self.assertEqual(ctx.returncode, 0)
def test_run_no_warnings_and_strict(self):
path = os.path.join(self.wd, 'warn.yaml')
with RunContext(self) as ctx:
cli.run((path, '--no-warnings', '-s'))
self.assertEqual(ctx.returncode, 2)
def test_run_non_universal_newline(self):
path = os.path.join(self.wd, 'dos.yml')
with RunContext(self) as ctx:
cli.run(('-d', 'rules:\n new-lines:\n type: dos', path))
self.assertEqual((ctx.returncode, ctx.stdout, ctx.stderr), (0, '', ''))
with RunContext(self) as ctx:
cli.run(('-d', 'rules:\n new-lines:\n type: unix', path))
expected_out = (
'%s\n'
' 1:4 error wrong new line character: expected \\n'
' (new-lines)\n'
'\n' % path)
self.assertEqual(
(ctx.returncode, ctx.stdout, ctx.stderr), (1, expected_out, ''))

View File

@@ -14,8 +14,19 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
import os
import shutil
import sys
import tempfile
import unittest import unittest
from tests.common import build_temp_workspace
from yamllint import cli
from yamllint import config from yamllint import config
@@ -30,7 +41,7 @@ class SimpleConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['colons']['max-spaces-before'], 0) self.assertEqual(new.rules['colons']['max-spaces-before'], 0)
self.assertEqual(new.rules['colons']['max-spaces-after'], 1) self.assertEqual(new.rules['colons']['max-spaces-after'], 1)
self.assertEqual(len(new.enabled_rules()), 1) self.assertEqual(len(new.enabled_rules(None)), 1)
def test_invalid_conf(self): def test_invalid_conf(self):
with self.assertRaises(config.YamlLintConfigError): with self.assertRaises(config.YamlLintConfigError):
@@ -44,13 +55,16 @@ class SimpleConfigTestCase(unittest.TestCase):
' this-one-does-not-exist: enable\n') ' this-one-does-not-exist: enable\n')
def test_missing_option(self): def test_missing_option(self):
with self.assertRaisesRegexp( c = config.YamlLintConfig('rules:\n'
config.YamlLintConfigError, ' colons: enable\n')
'invalid config: missing option "max-spaces-before" ' self.assertEqual(c.rules['colons']['max-spaces-before'], 0)
'for rule "colons"'): self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
config.YamlLintConfig('rules:\n'
c = config.YamlLintConfig('rules:\n'
' colons:\n' ' colons:\n'
' max-spaces-after: 1\n') ' max-spaces-before: 9\n')
self.assertEqual(c.rules['colons']['max-spaces-before'], 9)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
def test_unknown_option(self): def test_unknown_option(self):
with self.assertRaisesRegexp( with self.assertRaisesRegexp(
@@ -62,17 +76,61 @@ class SimpleConfigTestCase(unittest.TestCase):
' max-spaces-after: 1\n' ' max-spaces-after: 1\n'
' abcdef: yes\n') ' abcdef: yes\n')
def test_yes_no_for_booleans(self):
c = config.YamlLintConfig('rules:\n'
' indentation:\n'
' spaces: 2\n'
' indent-sequences: true\n'
' check-multi-line-strings: false\n')
self.assertTrue(c.rules['indentation']['indent-sequences'])
self.assertEqual(c.rules['indentation']['check-multi-line-strings'],
False)
c = config.YamlLintConfig('rules:\n'
' indentation:\n'
' spaces: 2\n'
' indent-sequences: yes\n'
' check-multi-line-strings: false\n')
self.assertTrue(c.rules['indentation']['indent-sequences'])
self.assertEqual(c.rules['indentation']['check-multi-line-strings'],
False)
c = config.YamlLintConfig('rules:\n'
' indentation:\n'
' spaces: 2\n'
' indent-sequences: whatever\n'
' check-multi-line-strings: false\n')
self.assertEqual(c.rules['indentation']['indent-sequences'],
'whatever')
self.assertEqual(c.rules['indentation']['check-multi-line-strings'],
False)
with self.assertRaisesRegexp(
config.YamlLintConfigError,
'invalid config: option "indent-sequences" of "indentation" '
'should be in '):
c = config.YamlLintConfig('rules:\n'
' indentation:\n'
' spaces: 2\n'
' indent-sequences: YES!\n'
' check-multi-line-strings: false\n')
def test_enable_disable_keywords(self):
c = config.YamlLintConfig('rules:\n'
' colons: enable\n'
' hyphens: disable\n')
self.assertEqual(c.rules['colons'], {'level': 'error',
'max-spaces-after': 1,
'max-spaces-before': 0})
self.assertEqual(c.rules['hyphens'], False)
def test_validate_rule_conf(self): def test_validate_rule_conf(self):
class Rule(object): class Rule(object):
ID = 'fake' ID = 'fake'
self.assertEqual(config.validate_rule_conf(Rule, False), False) self.assertFalse(config.validate_rule_conf(Rule, False))
self.assertEqual(config.validate_rule_conf(Rule, 'disable'), False)
self.assertEqual(config.validate_rule_conf(Rule, {}), self.assertEqual(config.validate_rule_conf(Rule, {}),
{'level': 'error'}) {'level': 'error'})
self.assertEqual(config.validate_rule_conf(Rule, 'enable'),
{'level': 'error'})
config.validate_rule_conf(Rule, {'level': 'error'}) config.validate_rule_conf(Rule, {'level': 'error'})
config.validate_rule_conf(Rule, {'level': 'warning'}) config.validate_rule_conf(Rule, {'level': 'warning'})
@@ -80,22 +138,22 @@ class SimpleConfigTestCase(unittest.TestCase):
config.validate_rule_conf, Rule, {'level': 'warn'}) config.validate_rule_conf, Rule, {'level': 'warn'})
Rule.CONF = {'length': int} Rule.CONF = {'length': int}
Rule.DEFAULT = {'length': 80}
config.validate_rule_conf(Rule, {'length': 8}) config.validate_rule_conf(Rule, {'length': 8})
self.assertRaises(config.YamlLintConfigError, config.validate_rule_conf(Rule, {})
config.validate_rule_conf, Rule, {})
self.assertRaises(config.YamlLintConfigError, self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'height': 8}) config.validate_rule_conf, Rule, {'height': 8})
Rule.CONF = {'a': bool, 'b': int} Rule.CONF = {'a': bool, 'b': int}
Rule.DEFAULT = {'a': True, 'b': -42}
config.validate_rule_conf(Rule, {'a': True, 'b': 0}) config.validate_rule_conf(Rule, {'a': True, 'b': 0})
self.assertRaises(config.YamlLintConfigError, config.validate_rule_conf(Rule, {'a': True})
config.validate_rule_conf, Rule, {'a': True}) config.validate_rule_conf(Rule, {'b': 0})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'b': 0})
self.assertRaises(config.YamlLintConfigError, self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'a': 1, 'b': 0}) config.validate_rule_conf, Rule, {'a': 1, 'b': 0})
Rule.CONF = {'choice': (True, 88, 'str')} Rule.CONF = {'choice': (True, 88, 'str')}
Rule.DEFAULT = {'choice': 88}
config.validate_rule_conf(Rule, {'choice': True}) config.validate_rule_conf(Rule, {'choice': True})
config.validate_rule_conf(Rule, {'choice': 88}) config.validate_rule_conf(Rule, {'choice': 88})
config.validate_rule_conf(Rule, {'choice': 'str'}) config.validate_rule_conf(Rule, {'choice': 'str'})
@@ -107,16 +165,37 @@ class SimpleConfigTestCase(unittest.TestCase):
config.validate_rule_conf, Rule, {'choice': 'abc'}) config.validate_rule_conf, Rule, {'choice': 'abc'})
Rule.CONF = {'choice': (int, 'hardcoded')} Rule.CONF = {'choice': (int, 'hardcoded')}
Rule.DEFAULT = {'choice': 1337}
config.validate_rule_conf(Rule, {'choice': 42}) config.validate_rule_conf(Rule, {'choice': 42})
config.validate_rule_conf(Rule, {'choice': 'hardcoded'}) config.validate_rule_conf(Rule, {'choice': 'hardcoded'})
config.validate_rule_conf(Rule, {})
self.assertRaises(config.YamlLintConfigError, self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': False}) config.validate_rule_conf, Rule, {'choice': False})
self.assertRaises(config.YamlLintConfigError, self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': 'abc'}) config.validate_rule_conf, Rule, {'choice': 'abc'})
Rule.CONF = {'multiple': ['item1', 'item2', 'item3']}
Rule.DEFAULT = {'multiple': ['item1']}
config.validate_rule_conf(Rule, {'multiple': []})
config.validate_rule_conf(Rule, {'multiple': ['item2']})
config.validate_rule_conf(Rule, {'multiple': ['item2', 'item3']})
config.validate_rule_conf(Rule, {})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule,
{'multiple': 'item1'})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule,
{'multiple': ['']})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule,
{'multiple': ['item1', 4]})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule,
{'multiple': ['item4']})
class ExtendedConfigTestCase(unittest.TestCase): class ExtendedConfigTestCase(unittest.TestCase):
def test_extend_add_rule(self): def test_extend_on_object(self):
old = config.YamlLintConfig('rules:\n' old = config.YamlLintConfig('rules:\n'
' colons:\n' ' colons:\n'
' max-spaces-before: 0\n' ' max-spaces-before: 0\n'
@@ -131,62 +210,132 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['colons']['max-spaces-after'], 1) self.assertEqual(new.rules['colons']['max-spaces-after'], 1)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2) self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 2) self.assertEqual(len(new.enabled_rules(None)), 2)
def test_extend_on_file(self):
with tempfile.NamedTemporaryFile('w') as f:
f.write('rules:\n'
' colons:\n'
' max-spaces-before: 0\n'
' max-spaces-after: 1\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n'
'rules:\n'
' hyphens:\n'
' max-spaces-after: 2\n')
self.assertEqual(sorted(c.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(c.rules['colons']['max-spaces-before'], 0)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
self.assertEqual(c.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(c.enabled_rules(None)), 2)
def test_extend_remove_rule(self): def test_extend_remove_rule(self):
old = config.YamlLintConfig('rules:\n' with tempfile.NamedTemporaryFile('w') as f:
' colons:\n' f.write('rules:\n'
' max-spaces-before: 0\n' ' colons:\n'
' max-spaces-after: 1\n' ' max-spaces-before: 0\n'
' hyphens:\n' ' max-spaces-after: 1\n'
' max-spaces-after: 2\n') ' hyphens:\n'
new = config.YamlLintConfig('rules:\n' ' max-spaces-after: 2\n')
' colons: disable\n') f.flush()
new.extend(old) c = config.YamlLintConfig('extends: ' + f.name + '\n'
'rules:\n'
' colons: disable\n')
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens']) self.assertEqual(sorted(c.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons'], False) self.assertFalse(c.rules['colons'])
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2) self.assertEqual(c.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 1) self.assertEqual(len(c.enabled_rules(None)), 1)
def test_extend_edit_rule(self): def test_extend_edit_rule(self):
old = config.YamlLintConfig('rules:\n' with tempfile.NamedTemporaryFile('w') as f:
' colons:\n' f.write('rules:\n'
' max-spaces-before: 0\n' ' colons:\n'
' max-spaces-after: 1\n' ' max-spaces-before: 0\n'
' hyphens:\n' ' max-spaces-after: 1\n'
' max-spaces-after: 2\n') ' hyphens:\n'
new = config.YamlLintConfig('rules:\n' ' max-spaces-after: 2\n')
' colons:\n' f.flush()
' max-spaces-before: 3\n' c = config.YamlLintConfig('extends: ' + f.name + '\n'
' max-spaces-after: 4\n') 'rules:\n'
new.extend(old) ' colons:\n'
' max-spaces-before: 3\n'
' max-spaces-after: 4\n')
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens']) self.assertEqual(sorted(c.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons']['max-spaces-before'], 3) self.assertEqual(c.rules['colons']['max-spaces-before'], 3)
self.assertEqual(new.rules['colons']['max-spaces-after'], 4) self.assertEqual(c.rules['colons']['max-spaces-after'], 4)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2) self.assertEqual(c.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 2) self.assertEqual(len(c.enabled_rules(None)), 2)
def test_extend_reenable_rule(self): def test_extend_reenable_rule(self):
old = config.YamlLintConfig('rules:\n' with tempfile.NamedTemporaryFile('w') as f:
' colons:\n' f.write('rules:\n'
' max-spaces-before: 0\n' ' colons:\n'
' max-spaces-after: 1\n' ' max-spaces-before: 0\n'
' hyphens: disable\n') ' max-spaces-after: 1\n'
new = config.YamlLintConfig('rules:\n' ' hyphens: disable\n')
' hyphens:\n' f.flush()
' max-spaces-after: 2\n') c = config.YamlLintConfig('extends: ' + f.name + '\n'
new.extend(old) 'rules:\n'
' hyphens:\n'
' max-spaces-after: 2\n')
self.assertEqual(sorted(new.rules.keys()), ['colons', 'hyphens']) self.assertEqual(sorted(c.rules.keys()), ['colons', 'hyphens'])
self.assertEqual(new.rules['colons']['max-spaces-before'], 0) self.assertEqual(c.rules['colons']['max-spaces-before'], 0)
self.assertEqual(new.rules['colons']['max-spaces-after'], 1) self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2) self.assertEqual(c.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 2) self.assertEqual(len(c.enabled_rules(None)), 2)
def test_extend_recursive_default_values(self):
with tempfile.NamedTemporaryFile('w') as f:
f.write('rules:\n'
' braces:\n'
' max-spaces-inside: 1248\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n'
'rules:\n'
' braces:\n'
' min-spaces-inside-empty: 2357\n')
self.assertEqual(c.rules['braces']['min-spaces-inside'], 0)
self.assertEqual(c.rules['braces']['max-spaces-inside'], 1248)
self.assertEqual(c.rules['braces']['min-spaces-inside-empty'], 2357)
self.assertEqual(c.rules['braces']['max-spaces-inside-empty'], -1)
with tempfile.NamedTemporaryFile('w') as f:
f.write('rules:\n'
' colons:\n'
' max-spaces-before: 1337\n')
f.flush()
c = config.YamlLintConfig('extends: ' + f.name + '\n'
'rules:\n'
' colons: enable\n')
self.assertEqual(c.rules['colons']['max-spaces-before'], 1337)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
with tempfile.NamedTemporaryFile('w') as f1, \
tempfile.NamedTemporaryFile('w') as f2:
f1.write('rules:\n'
' colons:\n'
' max-spaces-before: 1337\n')
f1.flush()
f2.write('extends: ' + f1.name + '\n'
'rules:\n'
' colons: disable\n')
f2.flush()
c = config.YamlLintConfig('extends: ' + f2.name + '\n'
'rules:\n'
' colons: enable\n')
self.assertEqual(c.rules['colons']['max-spaces-before'], 0)
self.assertEqual(c.rules['colons']['max-spaces-after'], 1)
class ExtendedLibraryConfigTestCase(unittest.TestCase): class ExtendedLibraryConfigTestCase(unittest.TestCase):
@@ -218,6 +367,9 @@ class ExtendedLibraryConfigTestCase(unittest.TestCase):
self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys())) self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys()))
for rule in new.rules: for rule in new.rules:
self.assertEqual(new.rules[rule], old.rules[rule]) self.assertEqual(new.rules[rule], old.rules[rule])
self.assertEqual(new.rules['empty-lines']['max'], 42)
self.assertEqual(new.rules['empty-lines']['max-start'], 43)
self.assertEqual(new.rules['empty-lines']['max-end'], 44)
def test_extend_config_override_rule_partly(self): def test_extend_config_override_rule_partly(self):
old = config.YamlLintConfig('extends: default') old = config.YamlLintConfig('extends: default')
@@ -231,3 +383,98 @@ class ExtendedLibraryConfigTestCase(unittest.TestCase):
self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys())) self.assertEqual(sorted(new.rules.keys()), sorted(old.rules.keys()))
for rule in new.rules: for rule in new.rules:
self.assertEqual(new.rules[rule], old.rules[rule]) self.assertEqual(new.rules[rule], old.rules[rule])
self.assertEqual(new.rules['empty-lines']['max'], 2)
self.assertEqual(new.rules['empty-lines']['max-start'], 42)
self.assertEqual(new.rules['empty-lines']['max-end'], 0)
class IgnorePathConfigTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
super(IgnorePathConfigTestCase, cls).setUpClass()
bad_yaml = ('---\n'
'- key: val1\n'
' key: val2\n'
'- trailing space \n'
'- lonely hyphen\n')
cls.wd = build_temp_workspace({
'bin/file.lint-me-anyway.yaml': bad_yaml,
'bin/file.yaml': bad_yaml,
'file-at-root.yaml': bad_yaml,
'file.dont-lint-me.yaml': bad_yaml,
'ign-dup/file.yaml': bad_yaml,
'ign-dup/sub/dir/file.yaml': bad_yaml,
'ign-trail/file.yaml': bad_yaml,
'include/ign-dup/sub/dir/file.yaml': bad_yaml,
's/s/ign-trail/file.yaml': bad_yaml,
's/s/ign-trail/s/s/file.yaml': bad_yaml,
's/s/ign-trail/s/s/file2.lint-me-anyway.yaml': bad_yaml,
'.yamllint': 'ignore: |\n'
' *.dont-lint-me.yaml\n'
' /bin/\n'
' !/bin/*.lint-me-anyway.yaml\n'
'\n'
'extends: default\n'
'\n'
'rules:\n'
' key-duplicates:\n'
' ignore: |\n'
' /ign-dup\n'
' trailing-spaces:\n'
' ignore: |\n'
' ign-trail\n'
' !*.lint-me-anyway.yaml\n',
})
cls.backup_wd = os.getcwd()
os.chdir(cls.wd)
@classmethod
def tearDownClass(cls):
super(IgnorePathConfigTestCase, cls).tearDownClass()
os.chdir(cls.backup_wd)
shutil.rmtree(cls.wd)
def test_run_with_ignored_path(self):
sys.stdout = StringIO()
with self.assertRaises(SystemExit):
cli.run(('-f', 'parsable', '.'))
out = sys.stdout.getvalue()
out = '\n'.join(sorted(out.splitlines()))
docstart = '[warning] missing document start "---" (document-start)'
keydup = '[error] duplication of key "key" in mapping (key-duplicates)'
trailing = '[error] trailing spaces (trailing-spaces)'
hyphen = '[error] too many spaces after hyphen (hyphens)'
self.assertEqual(out, '\n'.join((
'./.yamllint:1:1: ' + docstart,
'./bin/file.lint-me-anyway.yaml:3:3: ' + keydup,
'./bin/file.lint-me-anyway.yaml:4:17: ' + trailing,
'./bin/file.lint-me-anyway.yaml:5:5: ' + hyphen,
'./file-at-root.yaml:3:3: ' + keydup,
'./file-at-root.yaml:4:17: ' + trailing,
'./file-at-root.yaml:5:5: ' + hyphen,
'./ign-dup/file.yaml:4:17: ' + trailing,
'./ign-dup/file.yaml:5:5: ' + hyphen,
'./ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./ign-trail/file.yaml:3:3: ' + keydup,
'./ign-trail/file.yaml:5:5: ' + hyphen,
'./include/ign-dup/sub/dir/file.yaml:3:3: ' + keydup,
'./include/ign-dup/sub/dir/file.yaml:4:17: ' + trailing,
'./include/ign-dup/sub/dir/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file.yaml:5:5: ' + hyphen,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:3:3: ' + keydup,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:4:17: ' + trailing,
'./s/s/ign-trail/s/s/file2.lint-me-anyway.yaml:5:5: ' + hyphen,
)))

View File

@@ -15,7 +15,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import io import io
import unittest import unittest
from yamllint.config import YamlLintConfig from yamllint.config import YamlLintConfig

86
tests/test_module.py Normal file
View File

@@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 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/>.
import os
import shutil
import subprocess
import tempfile
import sys
import unittest
PYTHON = sys.executable or 'python'
class ModuleTestCase(unittest.TestCase):
def setUp(self):
self.wd = tempfile.mkdtemp(prefix='yamllint-tests-')
# file with only one warning
with open(os.path.join(self.wd, 'warn.yaml'), 'w') as f:
f.write('key: value\n')
# file in dir
os.mkdir(os.path.join(self.wd, 'sub'))
with open(os.path.join(self.wd, 'sub', 'nok.yaml'), 'w') as f:
f.write('---\n'
'list: [ 1, 1, 2, 3, 5, 8] \n')
def tearDown(self):
shutil.rmtree(self.wd)
def test_run_module_no_args(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx:
subprocess.check_output([PYTHON, '-m', 'yamllint'],
stderr=subprocess.STDOUT)
self.assertEqual(ctx.exception.returncode, 2)
self.assertRegexpMatches(ctx.exception.output.decode(),
r'^usage: yamllint')
def test_run_module_on_bad_dir(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx:
subprocess.check_output([PYTHON, '-m', 'yamllint',
'/does/not/exist'],
stderr=subprocess.STDOUT)
self.assertRegexpMatches(ctx.exception.output.decode(),
r'No such file or directory')
def test_run_module_on_file(self):
out = subprocess.check_output(
[PYTHON, '-m', 'yamllint', os.path.join(self.wd, 'warn.yaml')])
lines = out.decode().splitlines()
self.assertIn('/warn.yaml', lines[0])
self.assertEqual('\n'.join(lines[1:]),
' 1:1 warning missing document start "---"'
' (document-start)\n')
def test_run_module_on_dir(self):
with self.assertRaises(subprocess.CalledProcessError) as ctx:
subprocess.check_output([PYTHON, '-m', 'yamllint', self.wd])
self.assertEqual(ctx.exception.returncode, 1)
files = ctx.exception.output.decode().split('\n\n')
self.assertIn(
'/warn.yaml\n'
' 1:1 warning missing document start "---"'
' (document-start)',
files[0])
self.assertIn(
'/sub/nok.yaml\n'
' 2:9 error too many spaces inside brackets'
' (brackets)\n'
' 2:27 error trailing spaces (trailing-spaces)',
files[1])

View File

@@ -65,12 +65,12 @@ class ParserTestCase(unittest.TestCase):
def test_token_or_comment_generator(self): def test_token_or_comment_generator(self):
e = list(token_or_comment_generator('')) e = list(token_or_comment_generator(''))
self.assertEqual(len(e), 2) self.assertEqual(len(e), 2)
self.assertEqual(e[0].prev, None) self.assertIsNone(e[0].prev)
self.assertIsInstance(e[0].curr, yaml.Token) self.assertIsInstance(e[0].curr, yaml.Token)
self.assertIsInstance(e[0].next, yaml.Token) self.assertIsInstance(e[0].next, yaml.Token)
self.assertEqual(e[1].prev, e[0].curr) self.assertEqual(e[1].prev, e[0].curr)
self.assertEqual(e[1].curr, e[0].next) self.assertEqual(e[1].curr, e[0].next)
self.assertEqual(e[1].next, None) self.assertIsNone(e[1].next)
e = list(token_or_comment_generator('---\n' e = list(token_or_comment_generator('---\n'
'k: v\n')) 'k: v\n'))

View File

@@ -48,13 +48,14 @@ from tests.common import RuleTestCase
class SpecificationTestCase(RuleTestCase): class SpecificationTestCase(RuleTestCase):
rule_id = None rule_id = None
conf_general = ('document-start: disable\n' conf_general = ('document-start: disable\n'
'comments: {min-spaces-from-content: 1}\n' 'comments: {min-spaces-from-content: 1}\n'
'braces: {min-spaces-inside: 1, max-spaces-inside: 1}\n' 'braces: {min-spaces-inside: 1, max-spaces-inside: 1}\n'
'brackets: {min-spaces-inside: 1, max-spaces-inside: 1}\n') 'brackets: {min-spaces-inside: 1, max-spaces-inside: 1}\n')
conf_overrides = { conf_overrides = {
'example-2.2': ('colons: {max-spaces-after: 2}\n'), 'example-2.2': 'colons: {max-spaces-after: 2}\n',
'example-2.4': ('colons: {max-spaces-after: 3}\n'), 'example-2.4': 'colons: {max-spaces-after: 3}\n',
'example-2.5': ('empty-lines: {max-end: 2}\n' 'example-2.5': ('empty-lines: {max-end: 2}\n'
'brackets: {min-spaces-inside: 0, max-spaces-inside: 2}\n' 'brackets: {min-spaces-inside: 0, max-spaces-inside: 2}\n'
'commas: {max-spaces-before: -1}\n'), 'commas: {max-spaces-before: -1}\n'),
@@ -62,65 +63,65 @@ conf_overrides = {
'indentation: disable\n'), 'indentation: disable\n'),
'example-2.12': ('empty-lines: {max-end: 1}\n' 'example-2.12': ('empty-lines: {max-end: 1}\n'
'colons: {max-spaces-before: -1}\n'), 'colons: {max-spaces-before: -1}\n'),
'example-2.16': ('empty-lines: {max-end: 1}\n'), 'example-2.16': 'empty-lines: {max-end: 1}\n',
'example-2.18': ('empty-lines: {max-end: 1}\n'), 'example-2.18': 'empty-lines: {max-end: 1}\n',
'example-2.19': ('empty-lines: {max-end: 1}\n'), 'example-2.19': 'empty-lines: {max-end: 1}\n',
'example-2.28': ('empty-lines: {max-end: 3}\n'), 'example-2.28': 'empty-lines: {max-end: 3}\n',
'example-5.3': ('indentation: {indent-sequences: no}\n' 'example-5.3': ('indentation: {indent-sequences: false}\n'
'colons: {max-spaces-before: 1}\n'), 'colons: {max-spaces-before: 1}\n'),
'example-6.4': ('trailing-spaces: disable\n'), 'example-6.4': 'trailing-spaces: disable\n',
'example-6.5': ('trailing-spaces: disable\n'), 'example-6.5': 'trailing-spaces: disable\n',
'example-6.6': ('trailing-spaces: disable\n'), 'example-6.6': 'trailing-spaces: disable\n',
'example-6.7': ('trailing-spaces: disable\n'), 'example-6.7': 'trailing-spaces: disable\n',
'example-6.8': ('trailing-spaces: disable\n'), 'example-6.8': 'trailing-spaces: disable\n',
'example-6.10': ('empty-lines: {max-end: 2}\n' 'example-6.10': ('empty-lines: {max-end: 2}\n'
'trailing-spaces: disable\n' 'trailing-spaces: disable\n'
'comments-indentation: disable\n'), 'comments-indentation: disable\n'),
'example-6.11': ('empty-lines: {max-end: 1}\n' 'example-6.11': ('empty-lines: {max-end: 1}\n'
'comments-indentation: disable\n'), 'comments-indentation: disable\n'),
'example-6.13': ('comments-indentation: disable\n'), 'example-6.13': 'comments-indentation: disable\n',
'example-6.14': ('comments-indentation: disable\n'), 'example-6.14': 'comments-indentation: disable\n',
'example-6.23': ('colons: {max-spaces-before: 1}\n'), 'example-6.23': 'colons: {max-spaces-before: 1}\n',
'example-7.4': ('colons: {max-spaces-before: 1}\n' 'example-7.4': ('colons: {max-spaces-before: 1}\n'
'indentation: disable\n'), 'indentation: disable\n'),
'example-7.5': ('trailing-spaces: disable\n'), 'example-7.5': 'trailing-spaces: disable\n',
'example-7.6': ('trailing-spaces: disable\n'), 'example-7.6': 'trailing-spaces: disable\n',
'example-7.7': ('indentation: disable\n'), 'example-7.7': 'indentation: disable\n',
'example-7.8': ('colons: {max-spaces-before: 1}\n' 'example-7.8': ('colons: {max-spaces-before: 1}\n'
'indentation: disable\n'), 'indentation: disable\n'),
'example-7.9': ('trailing-spaces: disable\n'), 'example-7.9': 'trailing-spaces: disable\n',
'example-7.11': ('colons: {max-spaces-before: 1}\n' 'example-7.11': ('colons: {max-spaces-before: 1}\n'
'indentation: disable\n'), 'indentation: disable\n'),
'example-7.13': ('brackets: {min-spaces-inside: 0, max-spaces-inside: 1}\n' 'example-7.13': ('brackets: {min-spaces-inside: 0, max-spaces-inside: 1}\n'
'commas: {max-spaces-before: 1, min-spaces-after: 0}\n'), 'commas: {max-spaces-before: 1, min-spaces-after: 0}\n'),
'example-7.14': ('indentation: disable\n'), 'example-7.14': 'indentation: disable\n',
'example-7.15': ('braces: {min-spaces-inside: 0, max-spaces-inside: 1}\n' 'example-7.15': ('braces: {min-spaces-inside: 0, max-spaces-inside: 1}\n'
'commas: {max-spaces-before: 1, min-spaces-after: 0}\n' 'commas: {max-spaces-before: 1, min-spaces-after: 0}\n'
'colons: {max-spaces-before: 1}\n'), 'colons: {max-spaces-before: 1}\n'),
'example-7.16': ('indentation: disable\n'), 'example-7.16': 'indentation: disable\n',
'example-7.17': ('indentation: disable\n'), 'example-7.17': 'indentation: disable\n',
'example-7.18': ('indentation: disable\n'), 'example-7.18': 'indentation: disable\n',
'example-7.19': ('indentation: disable\n'), 'example-7.19': 'indentation: disable\n',
'example-7.20': ('colons: {max-spaces-before: 1}\n' 'example-7.20': ('colons: {max-spaces-before: 1}\n'
'indentation: disable\n'), 'indentation: disable\n'),
'example-8.1': ('empty-lines: {max-end: 1}\n'), 'example-8.1': 'empty-lines: {max-end: 1}\n',
'example-8.2': ('trailing-spaces: disable\n'), 'example-8.2': 'trailing-spaces: disable\n',
'example-8.5': ('comments-indentation: disable\n' 'example-8.5': ('comments-indentation: disable\n'
'trailing-spaces: disable\n'), 'trailing-spaces: disable\n'),
'example-8.6': ('empty-lines: {max-end: 1}\n'), 'example-8.6': 'empty-lines: {max-end: 1}\n',
'example-8.7': ('empty-lines: {max-end: 1}\n'), 'example-8.7': 'empty-lines: {max-end: 1}\n',
'example-8.8': ('trailing-spaces: disable\n'), 'example-8.8': 'trailing-spaces: disable\n',
'example-8.9': ('empty-lines: {max-end: 1}\n'), 'example-8.9': 'empty-lines: {max-end: 1}\n',
'example-8.14': ('colons: {max-spaces-before: 1}\n'), 'example-8.14': 'colons: {max-spaces-before: 1}\n',
'example-8.16': ('indentation: {spaces: 1}\n'), 'example-8.16': 'indentation: {spaces: 1}\n',
'example-8.17': ('indentation: disable\n'), 'example-8.17': 'indentation: disable\n',
'example-8.20': ('indentation: {indent-sequences: no}\n' 'example-8.20': ('indentation: {indent-sequences: false}\n'
'colons: {max-spaces-before: 1}\n'), 'colons: {max-spaces-before: 1}\n'),
'example-8.22': ('indentation: disable\n'), 'example-8.22': 'indentation: disable\n',
'example-10.1': ('colons: {max-spaces-before: 2}\n'), 'example-10.1': 'colons: {max-spaces-before: 2}\n',
'example-10.2': ('indentation: {indent-sequences: no}\n'), 'example-10.2': 'indentation: {indent-sequences: false}\n',
'example-10.8': ('truthy: disable\n'), 'example-10.8': 'truthy: disable\n',
'example-10.9': ('truthy: disable\n'), 'example-10.9': 'truthy: disable\n',
} }
files = os.listdir(os.path.join(os.path.dirname(os.path.realpath(__file__)), files = os.listdir(os.path.join(os.path.dirname(os.path.realpath(__file__)),
@@ -133,6 +134,7 @@ def _gen_test(buffer, conf):
self.check(buffer, conf) self.check(buffer, conf)
return test return test
# The following tests are blacklisted (i.e. will not be checked against # The following tests are blacklisted (i.e. will not be checked against
# yamllint), because pyyaml is currently not able to parse the contents # yamllint), because pyyaml is currently not able to parse the contents
# (using yaml.parse()). # (using yaml.parse()).

View File

@@ -302,3 +302,104 @@ class YamllintDirectivesTestCase(RuleTestCase):
' c: [x]\n', ' c: [x]\n',
conf, conf,
problem=(6, 2, 'comments-indentation')) problem=(6, 2, 'comments-indentation'))
def test_disable_file_directive(self):
conf = ('comments: {min-spaces-from-content: 2}\n'
'comments-indentation: {}\n')
self.check('# yamllint disable-file\n'
'---\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf)
self.check('# yamllint disable-file\n'
'---\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf)
self.check('#yamllint disable-file\n'
'---\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf)
self.check('#yamllint disable-file \n'
'---\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf)
self.check('---\n'
'# yamllint disable-file\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf,
problem1=(3, 8, 'comments'),
problem2=(5, 2, 'comments-indentation'))
self.check('# yamllint disable-file: rules cannot be specified\n'
'---\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf,
problem1=(3, 8, 'comments'),
problem2=(5, 2, 'comments-indentation'))
self.check('AAAA yamllint disable-file\n'
'---\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf,
problem1=(1, 1, 'document-start'),
problem2=(3, 8, 'comments'),
problem3=(5, 2, 'comments-indentation'))
def test_disable_file_directive_not_at_first_position(self):
self.check('# yamllint disable-file\n'
'---\n'
'- bad : colon and spaces \n',
self.conf)
self.check('---\n'
'# yamllint disable-file\n'
'- bad : colon and spaces \n',
self.conf,
problem1=(3, 7, 'colons'),
problem2=(3, 26, 'trailing-spaces'))
def test_disable_file_directive_with_syntax_error(self):
self.check('# This file is not valid YAML (it is a Jinja template)\n'
'{% if extra_info %}\n'
'key1: value1\n'
'{% endif %}\n'
'key2: value2\n',
self.conf,
problem=(2, 2, 'syntax'))
self.check('# yamllint disable-file\n'
'# This file is not valid YAML (it is a Jinja template)\n'
'{% if extra_info %}\n'
'key1: value1\n'
'{% endif %}\n'
'key2: value2\n',
self.conf)
def test_disable_file_directive_with_dos_lines(self):
self.check('# yamllint disable-file\r\n'
'---\r\n'
'- bad : colon and spaces \r\n',
self.conf)
self.check('# yamllint disable-file\r\n'
'# This file is not valid YAML (it is a Jinja template)\r\n'
'{% if extra_info %}\r\n'
'key1: value1\r\n'
'{% endif %}\r\n'
'key2: value2\r\n',
self.conf)

View File

@@ -22,7 +22,7 @@ indentation, etc."""
APP_NAME = 'yamllint' APP_NAME = 'yamllint'
APP_VERSION = '1.5.0' APP_VERSION = '1.23.0'
APP_DESCRIPTION = __doc__ APP_DESCRIPTION = __doc__
__author__ = u'Adrien Vergé' __author__ = u'Adrien Vergé'

4
yamllint/__main__.py Normal file
View File

@@ -0,0 +1,4 @@
from yamllint.cli import run
if __name__ == '__main__':
run()

View File

@@ -15,27 +15,40 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function from __future__ import print_function
import os.path
import sys
import argparse import argparse
import io
import os
import platform
import sys
from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION from yamllint import APP_DESCRIPTION, APP_NAME, APP_VERSION
from yamllint.config import YamlLintConfig, YamlLintConfigError
from yamllint import linter from yamllint import linter
from yamllint.config import YamlLintConfig, YamlLintConfigError
from yamllint.linter import PROBLEM_LEVELS
def find_files_recursively(items): def find_files_recursively(items, conf):
for item in items: for item in items:
if os.path.isdir(item): if os.path.isdir(item):
for root, dirnames, filenames in os.walk(item): for root, dirnames, filenames in os.walk(item):
for filename in [f for f in filenames for f in filenames:
if f.endswith(('.yml', '.yaml'))]: filepath = os.path.join(root, f)
yield os.path.join(root, filename) if conf.is_yaml_file(filepath):
yield filepath
else: else:
yield item 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): class Format(object):
@staticmethod @staticmethod
def parsable(problem, filename): def parsable(problem, filename):
@@ -72,31 +85,65 @@ class Format(object):
return line return line
def show_problems(problems, file, args_format, no_warn):
max_level = 0
first = True
for problem in problems:
max_level = max(max_level, PROBLEM_LEVELS[problem.level])
if no_warn and (problem.level != 'error'):
continue
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))
if not first and args_format != 'parsable':
print('')
return max_level
def run(argv=None): def run(argv=None):
parser = argparse.ArgumentParser(prog=APP_NAME, parser = argparse.ArgumentParser(prog=APP_NAME,
description=APP_DESCRIPTION) description=APP_DESCRIPTION)
parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+', files_group = parser.add_mutually_exclusive_group(required=True)
help='files to check') files_group.add_argument('files', metavar='FILE_OR_DIR', nargs='*',
parser.add_argument('-c', '--config-file', dest='config_file', default=(),
action='store', help='path to a custom configuration') help='files to check')
parser.add_argument('-d', '--config-data', dest='config_data', files_group.add_argument('-', action='store_true', dest='stdin',
action='store', help='read from standard input')
help='custom configuration (as YAML source)') 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', parser.add_argument('-f', '--format',
choices=('parsable', 'standard'), default='standard', choices=('parsable', 'standard', 'colored', 'auto'),
help='format for parsing output') 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('--no-warnings',
action='store_true',
help='output only error level problems')
parser.add_argument('-v', '--version', action='version', parser.add_argument('-v', '--version', action='version',
version='%s %s' % (APP_NAME, APP_VERSION)) version='{} {}'.format(APP_NAME, APP_VERSION))
# TODO: read from stdin when no filename?
args = parser.parse_args(argv) args = parser.parse_args(argv)
if args.config_file is not None and args.config_data is not None:
print('Options --config-file and --config-data cannot be used '
'simultaneously.', file=sys.stderr)
sys.exit(-1)
# User-global config is supposed to be in ~/.config/yamllint/config # User-global config is supposed to be in ~/.config/yamllint/config
if 'XDG_CONFIG_HOME' in os.environ: if 'XDG_CONFIG_HOME' in os.environ:
user_global_config = os.path.join( user_global_config = os.path.join(
@@ -113,6 +160,10 @@ def run(argv=None):
conf = YamlLintConfig(file=args.config_file) conf = YamlLintConfig(file=args.config_file)
elif os.path.isfile('.yamllint'): elif os.path.isfile('.yamllint'):
conf = YamlLintConfig(file='.yamllint') conf = YamlLintConfig(file='.yamllint')
elif os.path.isfile('.yamllint.yaml'):
conf = YamlLintConfig(file='.yamllint.yaml')
elif os.path.isfile('.yamllint.yml'):
conf = YamlLintConfig(file='.yamllint.yml')
elif os.path.isfile(user_global_config): elif os.path.isfile(user_global_config):
conf = YamlLintConfig(file=user_global_config) conf = YamlLintConfig(file=user_global_config)
else: else:
@@ -121,35 +172,36 @@ def run(argv=None):
print(e, file=sys.stderr) print(e, file=sys.stderr)
sys.exit(-1) sys.exit(-1)
return_code = 0 max_level = 0
for file in find_files_recursively(args.files): for file in find_files_recursively(args.files, conf):
filepath = file[2:] if file.startswith('./') else file
try: try:
first = True with io.open(file, newline='') as f:
with open(file) as f: problems = linter.run(f, conf, filepath)
for problem in linter.run(f, conf):
if args.format == 'parsable':
print(Format.parsable(problem, file))
elif sys.stdout.isatty():
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))
if return_code == 0 and problem.level == 'error':
return_code = 1
if not first and args.format != 'parsable':
print('')
except EnvironmentError as e: except EnvironmentError as e:
print(e, file=sys.stderr) print(e, file=sys.stderr)
return_code = -1 sys.exit(-1)
prob_level = show_problems(problems, file, args_format=args.format,
no_warn=args.no_warnings)
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,
no_warn=args.no_warnings)
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) sys.exit(return_code)

View File

@@ -1,47 +1,33 @@
--- ---
yaml-files:
- '*.yaml'
- '*.yml'
- '.yamllint'
rules: rules:
braces: braces: enable
min-spaces-inside: 0 brackets: enable
max-spaces-inside: 0 colons: enable
brackets: commas: enable
min-spaces-inside: 0
max-spaces-inside: 0
colons:
max-spaces-before: 0
max-spaces-after: 1
commas:
max-spaces-before: 0
min-spaces-after: 1
max-spaces-after: 1
comments: comments:
level: warning level: warning
require-starting-space: yes
min-spaces-from-content: 2
comments-indentation: comments-indentation:
level: warning level: warning
document-end: disable document-end: disable
document-start: document-start:
level: warning level: warning
present: yes empty-lines: enable
empty-lines: empty-values: disable
max: 2 hyphens: enable
max-start: 0 indentation: enable
max-end: 0
hyphens:
max-spaces-after: 1
indentation:
spaces: consistent
indent-sequences: yes
check-multi-line-strings: no
key-duplicates: enable key-duplicates: enable
line-length: key-ordering: disable
max: 80 line-length: enable
allow-non-breakable-words: yes
allow-non-breakable-inline-mappings: no
new-line-at-end-of-file: enable new-line-at-end-of-file: enable
new-lines: new-lines: enable
type: unix octal-values: disable
quoted-strings: disable
trailing-spaces: enable trailing-spaces: enable
truthy: truthy:
level: warning level: warning

View File

@@ -25,5 +25,5 @@ rules:
indent-sequences: consistent indent-sequences: consistent
line-length: line-length:
level: warning level: warning
allow-non-breakable-inline-mappings: yes allow-non-breakable-inline-mappings: true
truthy: disable truthy: disable

View File

@@ -16,6 +16,7 @@
import os.path import os.path
import pathspec
import yaml import yaml
import yamllint.rules import yamllint.rules
@@ -29,6 +30,11 @@ class YamlLintConfig(object):
def __init__(self, content=None, file=None): def __init__(self, content=None, file=None):
assert (content is None) ^ (file is None) assert (content is None) ^ (file is None)
self.ignore = None
self.yaml_files = pathspec.PathSpec.from_lines(
'gitwildmatch', ['*.yaml', '*.yml', '.yamllint'])
if file is not None: if file is not None:
with open(file) as f: with open(file) as f:
content = f.read() content = f.read()
@@ -36,15 +42,23 @@ class YamlLintConfig(object):
self.parse(content) self.parse(content)
self.validate() self.validate()
def enabled_rules(self): def is_file_ignored(self, filepath):
return self.ignore and self.ignore.match_file(filepath)
def is_yaml_file(self, filepath):
return self.yaml_files.match_file(filepath)
def enabled_rules(self, filepath):
return [yamllint.rules.get(id) for id, val in self.rules.items() return [yamllint.rules.get(id) for id, val in self.rules.items()
if val is not False] if val is not False and (
filepath is None or 'ignore' not in val or
not val['ignore'].match_file(filepath))]
def extend(self, base_config): def extend(self, base_config):
assert isinstance(base_config, YamlLintConfig) assert isinstance(base_config, YamlLintConfig)
for rule in self.rules: for rule in self.rules:
if (type(self.rules[rule]) == dict and if (isinstance(self.rules[rule], dict) and
rule in base_config.rules and rule in base_config.rules and
base_config.rules[rule] is not False): base_config.rules[rule] is not False):
base_config.rules[rule].update(self.rules[rule]) base_config.rules[rule].update(self.rules[rule])
@@ -53,16 +67,24 @@ class YamlLintConfig(object):
self.rules = base_config.rules self.rules = base_config.rules
if base_config.ignore is not None:
self.ignore = base_config.ignore
def parse(self, raw_content): def parse(self, raw_content):
try: try:
conf = yaml.safe_load(raw_content) conf = yaml.safe_load(raw_content)
except Exception as e: except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e) raise YamlLintConfigError('invalid config: %s' % e)
if type(conf) != dict: if not isinstance(conf, dict):
raise YamlLintConfigError('invalid config: not a dict') raise YamlLintConfigError('invalid config: not a dict')
self.rules = conf.get('rules', {}) self.rules = conf.get('rules', {})
for rule in self.rules:
if self.rules[rule] == 'enable':
self.rules[rule] = {}
elif self.rules[rule] == 'disable':
self.rules[rule] = False
# Does this conf override another conf that we need to load? # Does this conf override another conf that we need to load?
if 'extends' in conf: if 'extends' in conf:
@@ -73,6 +95,22 @@ class YamlLintConfig(object):
except Exception as e: except Exception as e:
raise YamlLintConfigError('invalid config: %s' % e) raise YamlLintConfigError('invalid config: %s' % e)
if 'ignore' in conf:
if not isinstance(conf['ignore'], str):
raise YamlLintConfigError(
'invalid config: ignore should contain file patterns')
self.ignore = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
if 'yaml-files' in conf:
if not (isinstance(conf['yaml-files'], list)
and all(isinstance(i, str) for i in conf['yaml-files'])):
raise YamlLintConfigError(
'invalid config: yaml-files '
'should be a list of file patterns')
self.yaml_files = pathspec.PathSpec.from_lines('gitwildmatch',
conf['yaml-files'])
def validate(self): def validate(self):
for id in self.rules: for id in self.rules:
try: try:
@@ -84,12 +122,18 @@ class YamlLintConfig(object):
def validate_rule_conf(rule, conf): def validate_rule_conf(rule, conf):
if conf is False or conf == 'disable': if conf is False: # disable
return False return False
elif conf == 'enable':
conf = {}
if type(conf) == dict: if isinstance(conf, dict):
if ('ignore' in conf and
not isinstance(conf['ignore'], pathspec.pathspec.PathSpec)):
if not isinstance(conf['ignore'], str):
raise YamlLintConfigError(
'invalid config: ignore should contain file patterns')
conf['ignore'] = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
if 'level' not in conf: if 'level' not in conf:
conf['level'] = 'error' conf['level'] = 'error'
elif conf['level'] not in ('error', 'warning'): elif conf['level'] not in ('error', 'warning'):
@@ -97,29 +141,49 @@ def validate_rule_conf(rule, conf):
'invalid config: level should be "error" or "warning"') 'invalid config: level should be "error" or "warning"')
options = getattr(rule, 'CONF', {}) options = getattr(rule, 'CONF', {})
options_default = getattr(rule, 'DEFAULT', {})
for optkey in conf: for optkey in conf:
if optkey == 'level': if optkey in ('ignore', 'level'):
continue continue
if optkey not in options: if optkey not in options:
raise YamlLintConfigError( raise YamlLintConfigError(
'invalid config: unknown option "%s" for rule "%s"' % 'invalid config: unknown option "%s" for rule "%s"' %
(optkey, rule.ID)) (optkey, rule.ID))
if type(options[optkey]) == tuple: # Example: CONF = {option: (bool, 'mixed')}
# → {option: true} → {option: mixed}
if isinstance(options[optkey], tuple):
if (conf[optkey] not in options[optkey] and if (conf[optkey] not in options[optkey] and
type(conf[optkey]) not in options[optkey]): type(conf[optkey]) not in options[optkey]):
raise YamlLintConfigError( raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be in %s' 'invalid config: option "%s" of "%s" should be in %s'
% (optkey, rule.ID, options[optkey])) % (optkey, rule.ID, options[optkey]))
# Example: CONF = {option: ['flag1', 'flag2', int]}
# → {option: [flag1]} → {option: [42, flag1, flag2]}
elif isinstance(options[optkey], list):
if (type(conf[optkey]) is not list or
any(flag not in options[optkey] and
type(flag) not in options[optkey]
for flag in conf[optkey])):
raise YamlLintConfigError(
('invalid config: option "%s" of "%s" should only '
'contain values in %s')
% (optkey, rule.ID, str(options[optkey])))
# Example: CONF = {option: int}
# → {option: 42}
else: else:
if type(conf[optkey]) != options[optkey]: if not isinstance(conf[optkey], options[optkey]):
raise YamlLintConfigError( raise YamlLintConfigError(
'invalid config: option "%s" of "%s" should be %s' 'invalid config: option "%s" of "%s" should be %s'
% (optkey, rule.ID, options[optkey].__name__)) % (optkey, rule.ID, options[optkey].__name__))
for optkey in options: for optkey in options:
if optkey not in conf: if optkey not in conf:
raise YamlLintConfigError( conf[optkey] = options_default[optkey]
'invalid config: missing option "%s" for rule "%s"' %
(optkey, rule.ID)) if hasattr(rule, 'VALIDATE'):
res = rule.VALIDATE(conf)
if res:
raise YamlLintConfigError('invalid config: %s: %s' %
(rule.ID, res))
else: else:
raise YamlLintConfigError(('invalid config: rule "%s": should be ' raise YamlLintConfigError(('invalid config: rule "%s": should be '
'either "enable", "disable" or a dict') 'either "enable", "disable" or a dict')

View File

@@ -21,6 +21,16 @@ import yaml
from yamllint import parser from yamllint import parser
PROBLEM_LEVELS = {
0: None,
1: 'warning',
2: 'error',
None: 0,
'warning': 1,
'error': 2,
}
class LintProblem(object): class LintProblem(object):
"""Represents a linting problem found by yamllint.""" """Represents a linting problem found by yamllint."""
def __init__(self, line, column, desc='<no description>', rule=None): def __init__(self, line, column, desc='<no description>', rule=None):
@@ -37,7 +47,7 @@ class LintProblem(object):
@property @property
def message(self): def message(self):
if self.rule is not None: if self.rule is not None:
return '%s (%s)' % (self.desc, self.rule) return '{} ({})'.format(self.desc, self.rule)
return self.desc return self.desc
def __eq__(self, other): def __eq__(self, other):
@@ -53,8 +63,8 @@ class LintProblem(object):
return '%d:%d: %s' % (self.line, self.column, self.message) return '%d:%d: %s' % (self.line, self.column, self.message)
def get_costemic_problems(buffer, conf): def get_cosmetic_problems(buffer, conf, filepath):
rules = conf.enabled_rules() rules = conf.enabled_rules(filepath)
# Split token rules from line rules # Split token rules from line rules
token_rules = [r for r in rules if r.TYPE == 'token'] token_rules = [r for r in rules if r.TYPE == 'token']
@@ -65,10 +75,10 @@ def get_costemic_problems(buffer, conf):
for rule in token_rules: for rule in token_rules:
context[rule.ID] = {} context[rule.ID] = {}
class DisableDirective(): class DisableDirective:
def __init__(self): def __init__(self):
self.rules = set() self.rules = set()
self.all_rules = set([r.ID for r in rules]) self.all_rules = {r.ID for r in rules}
def process_comment(self, comment): def process_comment(self, comment):
try: try:
@@ -170,20 +180,24 @@ def get_syntax_error(buffer):
except yaml.error.MarkedYAMLError as e: except yaml.error.MarkedYAMLError as e:
problem = LintProblem(e.problem_mark.line + 1, problem = LintProblem(e.problem_mark.line + 1,
e.problem_mark.column + 1, e.problem_mark.column + 1,
'syntax error: ' + e.problem) 'syntax error: ' + e.problem + ' (syntax)')
problem.level = 'error' problem.level = 'error'
return problem return problem
def _run(buffer, conf): def _run(buffer, conf, filepath):
assert hasattr(buffer, '__getitem__'), \ assert hasattr(buffer, '__getitem__'), \
'_run() argument must be a buffer, not a stream' '_run() argument must be a buffer, not a stream'
first_line = next(parser.line_generator(buffer)).content
if re.match(r'^#\s*yamllint disable-file\s*$', first_line):
return
# If the document contains a syntax error, save it and yield it at the # If the document contains a syntax error, save it and yield it at the
# right line # right line
syntax_error = get_syntax_error(buffer) syntax_error = get_syntax_error(buffer)
for problem in get_costemic_problems(buffer, conf): for problem in get_cosmetic_problems(buffer, conf, filepath):
# Insert the syntax error (if any) at the right place... # Insert the syntax error (if any) at the right place...
if (syntax_error and syntax_error.line <= problem.line and if (syntax_error and syntax_error.line <= problem.line and
syntax_error.column <= problem.column): syntax_error.column <= problem.column):
@@ -205,7 +219,7 @@ def _run(buffer, conf):
yield syntax_error yield syntax_error
def run(input, conf): def run(input, conf, filepath=None):
"""Lints a YAML source. """Lints a YAML source.
Returns a generator of LintProblem objects. Returns a generator of LintProblem objects.
@@ -213,11 +227,14 @@ def run(input, conf):
:param input: buffer, string or stream to read from :param input: buffer, string or stream to read from
:param conf: yamllint configuration object :param conf: yamllint configuration object
""" """
if type(input) in (type(b''), type(u'')): # compat with Python 2 & 3 if conf.is_file_ignored(filepath):
return _run(input, conf) return ()
if isinstance(input, (type(b''), type(u''))): # compat with Python 2 & 3
return _run(input, conf, filepath)
elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase elif hasattr(input, 'read'): # Python 2's file or Python 3's io.IOBase
# We need to have everything in memory to parse correctly # We need to have everything in memory to parse correctly
content = input.read() content = input.read()
return _run(content, conf) return _run(content, conf, filepath)
else: else:
raise TypeError('input should be a string or a stream') raise TypeError('input should be a string or a stream')

View File

@@ -77,7 +77,10 @@ def line_generator(buffer):
cur = 0 cur = 0
next = buffer.find('\n') next = buffer.find('\n')
while next != -1: while next != -1:
yield Line(line_no, buffer, start=cur, end=next) if next > 0 and buffer[next - 1] == '\r':
yield Line(line_no, buffer, start=cur, end=next - 1)
else:
yield Line(line_no, buffer, start=cur, end=next)
cur = next + 1 cur = next + 1
next = buffer.find('\n', cur) next = buffer.find('\n', cur)
line_no += 1 line_no += 1
@@ -125,7 +128,8 @@ def token_or_comment_generator(buffer):
curr = yaml_loader.get_token() curr = yaml_loader.get_token()
while curr is not None: while curr is not None:
next = yaml_loader.get_token() next = yaml_loader.get_token()
nextnext = yaml_loader.peek_token() nextnext = (yaml_loader.peek_token()
if yaml_loader.check_token() else None)
yield Token(curr.start_mark.line + 1, curr, prev, next, nextnext) yield Token(curr.start_mark.line + 1, curr, prev, next, nextnext)

View File

@@ -24,12 +24,16 @@ from yamllint.rules import (
document_end, document_end,
document_start, document_start,
empty_lines, empty_lines,
empty_values,
hyphens, hyphens,
indentation, indentation,
key_duplicates, key_duplicates,
key_ordering,
line_length, line_length,
new_line_at_end_of_file, new_line_at_end_of_file,
new_lines, new_lines,
octal_values,
quoted_strings,
trailing_spaces, trailing_spaces,
truthy, truthy,
) )
@@ -44,12 +48,16 @@ _RULES = {
document_end.ID: document_end, document_end.ID: document_end,
document_start.ID: document_start, document_start.ID: document_start,
empty_lines.ID: empty_lines, empty_lines.ID: empty_lines,
empty_values.ID: empty_values,
hyphens.ID: hyphens, hyphens.ID: hyphens,
indentation.ID: indentation, indentation.ID: indentation,
key_duplicates.ID: key_duplicates, key_duplicates.ID: key_duplicates,
key_ordering.ID: key_ordering,
line_length.ID: line_length, line_length.ID: line_length,
new_line_at_end_of_file.ID: new_line_at_end_of_file, new_line_at_end_of_file.ID: new_line_at_end_of_file,
new_lines.ID: new_lines, new_lines.ID: new_lines,
octal_values.ID: octal_values,
quoted_strings.ID: quoted_strings,
trailing_spaces.ID: trailing_spaces, trailing_spaces.ID: trailing_spaces,
truthy.ID: truthy, truthy.ID: truthy,
} }

View File

@@ -23,6 +23,10 @@ Use this rule to control the number of spaces inside braces (``{`` and ``}``).
braces. braces.
* ``max-spaces-inside`` defines the maximal number of spaces allowed inside * ``max-spaces-inside`` defines the maximal number of spaces allowed inside
braces. braces.
* ``min-spaces-inside-empty`` defines the minimal number of spaces required
inside empty braces.
* ``max-spaces-inside-empty`` defines the maximal number of spaces allowed
inside empty braces.
.. rubric:: Examples .. rubric:: Examples
@@ -59,6 +63,30 @@ Use this rule to control the number of spaces inside braces (``{`` and ``}``).
:: ::
object: {key1: 4, key2: 8 } object: {key1: 4, key2: 8 }
#. With ``braces: {min-spaces-inside-empty: 0, max-spaces-inside-empty: 0}``
the following code snippet would **PASS**:
::
object: {}
the following code snippet would **FAIL**:
::
object: { }
#. With ``braces: {min-spaces-inside-empty: 1, max-spaces-inside-empty: -1}``
the following code snippet would **PASS**:
::
object: { }
the following code snippet would **FAIL**:
::
object: {}
""" """
@@ -70,11 +98,31 @@ from yamllint.rules.common import spaces_after, spaces_before
ID = 'braces' ID = 'braces'
TYPE = 'token' TYPE = 'token'
CONF = {'min-spaces-inside': int, CONF = {'min-spaces-inside': int,
'max-spaces-inside': int} 'max-spaces-inside': int,
'min-spaces-inside-empty': int,
'max-spaces-inside-empty': int}
DEFAULT = {'min-spaces-inside': 0,
'max-spaces-inside': 0,
'min-spaces-inside-empty': -1,
'max-spaces-inside-empty': -1}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.FlowMappingStartToken): if (isinstance(token, yaml.FlowMappingStartToken) and
isinstance(next, yaml.FlowMappingEndToken)):
problem = spaces_after(token, prev, next,
min=(conf['min-spaces-inside-empty']
if conf['min-spaces-inside-empty'] != -1
else conf['min-spaces-inside']),
max=(conf['max-spaces-inside-empty']
if conf['max-spaces-inside-empty'] != -1
else conf['max-spaces-inside']),
min_desc='too few spaces inside empty braces',
max_desc='too many spaces inside empty braces')
if problem is not None:
yield problem
elif isinstance(token, yaml.FlowMappingStartToken):
problem = spaces_after(token, prev, next, problem = spaces_after(token, prev, next,
min=conf['min-spaces-inside'], min=conf['min-spaces-inside'],
max=conf['max-spaces-inside'], max=conf['max-spaces-inside'],

View File

@@ -24,6 +24,10 @@ Use this rule to control the number of spaces inside brackets (``[`` and
brackets. brackets.
* ``max-spaces-inside`` defines the maximal number of spaces allowed inside * ``max-spaces-inside`` defines the maximal number of spaces allowed inside
brackets. brackets.
* ``min-spaces-inside-empty`` defines the minimal number of spaces required
inside empty brackets.
* ``max-spaces-inside-empty`` defines the maximal number of spaces allowed
inside empty brackets.
.. rubric:: Examples .. rubric:: Examples
@@ -60,6 +64,30 @@ Use this rule to control the number of spaces inside brackets (``[`` and
:: ::
object: [1, 2, abc ] object: [1, 2, abc ]
#. With ``brackets: {min-spaces-inside-empty: 0, max-spaces-inside-empty: 0}``
the following code snippet would **PASS**:
::
object: []
the following code snippet would **FAIL**:
::
object: [ ]
#. With ``brackets: {min-spaces-inside-empty: 1, max-spaces-inside-empty: -1}``
the following code snippet would **PASS**:
::
object: [ ]
the following code snippet would **FAIL**:
::
object: []
""" """
@@ -71,11 +99,32 @@ from yamllint.rules.common import spaces_after, spaces_before
ID = 'brackets' ID = 'brackets'
TYPE = 'token' TYPE = 'token'
CONF = {'min-spaces-inside': int, CONF = {'min-spaces-inside': int,
'max-spaces-inside': int} 'max-spaces-inside': int,
'min-spaces-inside-empty': int,
'max-spaces-inside-empty': int}
DEFAULT = {'min-spaces-inside': 0,
'max-spaces-inside': 0,
'min-spaces-inside-empty': -1,
'max-spaces-inside-empty': -1}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.FlowSequenceStartToken): if (isinstance(token, yaml.FlowSequenceStartToken) and
isinstance(next, yaml.FlowSequenceEndToken)):
problem = spaces_after(token, prev, next,
min=(conf['min-spaces-inside-empty']
if conf['min-spaces-inside-empty'] != -1
else conf['min-spaces-inside']),
max=(conf['max-spaces-inside-empty']
if conf['max-spaces-inside-empty'] != -1
else conf['max-spaces-inside']),
min_desc='too few spaces inside empty brackets',
max_desc=('too many spaces inside empty '
'brackets'))
if problem is not None:
yield problem
elif isinstance(token, yaml.FlowSequenceStartToken):
problem = spaces_after(token, prev, next, problem = spaces_after(token, prev, next,
min=conf['min-spaces-inside'], min=conf['min-spaces-inside'],
max=conf['max-spaces-inside'], max=conf['max-spaces-inside'],

View File

@@ -72,13 +72,15 @@ Use this rule to control the number of spaces before and after colons (``:``).
import yaml import yaml
from yamllint.rules.common import spaces_after, spaces_before, is_explicit_key from yamllint.rules.common import is_explicit_key, spaces_after, spaces_before
ID = 'colons' ID = 'colons'
TYPE = 'token' TYPE = 'token'
CONF = {'max-spaces-before': int, CONF = {'max-spaces-before': int,
'max-spaces-after': int} 'max-spaces-after': int}
DEFAULT = {'max-spaces-before': 0,
'max-spaces-after': 1}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):

View File

@@ -103,6 +103,9 @@ TYPE = 'token'
CONF = {'max-spaces-before': int, CONF = {'max-spaces-before': int,
'min-spaces-after': int, 'min-spaces-after': int,
'max-spaces-after': int} 'max-spaces-after': int}
DEFAULT = {'max-spaces-before': 0,
'min-spaces-after': 1,
'max-spaces-after': 1}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):

View File

@@ -20,14 +20,17 @@ Use this rule to control the position and formatting of comments.
.. rubric:: Options .. rubric:: Options
* Use ``require-starting-space`` to require a space character right after the * Use ``require-starting-space`` to require a space character right after the
``#``. Set to ``yes`` to enable, ``no`` to disable. ``#``. Set to ``true`` to enable, ``false`` to disable.
* Use ``ignore-shebangs`` to ignore a
`shebang <https://en.wikipedia.org/wiki/Shebang_(Unix)>`_ at the beginning of
the file when ``require-starting-space`` is set.
* ``min-spaces-from-content`` is used to visually separate inline comments from * ``min-spaces-from-content`` is used to visually separate inline comments from
content. It defines the minimal required number of spaces between a comment content. It defines the minimal required number of spaces between a comment
and its preceding content. and its preceding content.
.. rubric:: Examples .. rubric:: Examples
#. With ``comments: {require-starting-space: yes}`` #. With ``comments: {require-starting-space: true}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
:: ::
@@ -61,13 +64,19 @@ Use this rule to control the position and formatting of comments.
""" """
import re
from yamllint.linter import LintProblem from yamllint.linter import LintProblem
ID = 'comments' ID = 'comments'
TYPE = 'comment' TYPE = 'comment'
CONF = {'require-starting-space': bool, CONF = {'require-starting-space': bool,
'ignore-shebangs': bool,
'min-spaces-from-content': int} 'min-spaces-from-content': int}
DEFAULT = {'require-starting-space': True,
'ignore-shebangs': True,
'min-spaces-from-content': 2}
def check(conf, comment): def check(conf, comment):
@@ -82,8 +91,14 @@ def check(conf, comment):
while (comment.buffer[text_start] == '#' and while (comment.buffer[text_start] == '#' and
text_start < len(comment.buffer)): text_start < len(comment.buffer)):
text_start += 1 text_start += 1
if (text_start < len(comment.buffer) and if text_start < len(comment.buffer):
comment.buffer[text_start] not in (' ', '\n', '\0')): if (conf['ignore-shebangs'] and
yield LintProblem(comment.line_no, comment.line_no == 1 and
comment.column_no + text_start - comment.pointer, comment.column_no == 1 and
'missing starting space in comment') re.match(r'^!\S', comment.buffer[text_start:])):
return
elif comment.buffer[text_start] not in (' ', '\n', '\0'):
column = comment.column_no + text_start - comment.pointer
yield LintProblem(comment.line_no,
column,
'missing starting space in comment')

View File

@@ -19,12 +19,12 @@ Use this rule to require or forbid the use of document end marker (``...``).
.. rubric:: Options .. rubric:: Options
* Set ``present`` to ``yes`` when the document end marker is required, or to * Set ``present`` to ``true`` when the document end marker is required, or to
``no`` when it is forbidden. ``false`` when it is forbidden.
.. rubric:: Examples .. rubric:: Examples
#. With ``document-end: {present: yes}`` #. With ``document-end: {present: true}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
:: ::
@@ -49,7 +49,7 @@ Use this rule to require or forbid the use of document end marker (``...``).
- is: another one - is: another one
... ...
#. With ``document-end: {present: no}`` #. With ``document-end: {present: false}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
:: ::
@@ -82,18 +82,21 @@ from yamllint.linter import LintProblem
ID = 'document-end' ID = 'document-end'
TYPE = 'token' TYPE = 'token'
CONF = {'present': bool} CONF = {'present': bool}
DEFAULT = {'present': True}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):
if conf['present']: if conf['present']:
if (isinstance(token, yaml.StreamEndToken) and is_stream_end = isinstance(token, yaml.StreamEndToken)
not (isinstance(prev, yaml.DocumentEndToken) or is_start = isinstance(token, yaml.DocumentStartToken)
isinstance(prev, yaml.StreamStartToken))): prev_is_end_or_stream_start = isinstance(
prev, (yaml.DocumentEndToken, yaml.StreamStartToken)
)
if is_stream_end and not prev_is_end_or_stream_start:
yield LintProblem(token.start_mark.line, 1, yield LintProblem(token.start_mark.line, 1,
'missing document end "..."') 'missing document end "..."')
elif (isinstance(token, yaml.DocumentStartToken) and elif is_start and not prev_is_end_or_stream_start:
not (isinstance(prev, yaml.DocumentEndToken) or
isinstance(prev, yaml.StreamStartToken))):
yield LintProblem(token.start_mark.line + 1, 1, yield LintProblem(token.start_mark.line + 1, 1,
'missing document end "..."') 'missing document end "..."')

View File

@@ -19,12 +19,12 @@ Use this rule to require or forbid the use of document start marker (``---``).
.. rubric:: Options .. rubric:: Options
* Set ``present`` to ``yes`` when the document start marker is required, or to * Set ``present`` to ``true`` when the document start marker is required, or to
``no`` when it is forbidden. ``false`` when it is forbidden.
.. rubric:: Examples .. rubric:: Examples
#. With ``document-start: {present: yes}`` #. With ``document-start: {present: true}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
:: ::
@@ -45,7 +45,7 @@ Use this rule to require or forbid the use of document start marker (``---``).
- this - this
- is: another one - is: another one
#. With ``document-start: {present: no}`` #. With ``document-start: {present: false}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
:: ::
@@ -72,6 +72,7 @@ from yamllint.linter import LintProblem
ID = 'document-start' ID = 'document-start'
TYPE = 'token' TYPE = 'token'
CONF = {'present': bool} CONF = {'present': bool}
DEFAULT = {'present': True}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):

View File

@@ -58,32 +58,45 @@ TYPE = 'line'
CONF = {'max': int, CONF = {'max': int,
'max-start': int, 'max-start': int,
'max-end': int} 'max-end': int}
DEFAULT = {'max': 2,
'max-start': 0,
'max-end': 0}
def check(conf, line): def check(conf, line):
if line.start == line.end and line.end < len(line.buffer): if line.start == line.end and line.end < len(line.buffer):
# Only alert on the last blank line of a series # Only alert on the last blank line of a series
if (line.end < len(line.buffer) - 1 and if (line.end + 2 <= len(line.buffer) and
line.buffer[line.end + 1] == '\n'): line.buffer[line.end:line.end + 2] == '\n\n'):
return
elif (line.end + 4 <= len(line.buffer) and
line.buffer[line.end:line.end + 4] == '\r\n\r\n'):
return return
blank_lines = 0 blank_lines = 0
while (line.start > blank_lines and start = line.start
line.buffer[line.start - blank_lines - 1] == '\n'): while start >= 2 and line.buffer[start - 2:start] == '\r\n':
blank_lines += 1 blank_lines += 1
start -= 2
while start >= 1 and line.buffer[start - 1] == '\n':
blank_lines += 1
start -= 1
max = conf['max'] max = conf['max']
# Special case: start of document # Special case: start of document
if line.start - blank_lines == 0: if start == 0:
blank_lines += 1 # first line doesn't have a preceding \n blank_lines += 1 # first line doesn't have a preceding \n
max = conf['max-start'] max = conf['max-start']
# Special case: end of document # Special case: end of document
# NOTE: The last line of a file is always supposed to end with a new # NOTE: The last line of a file is always supposed to end with a new
# line. See POSIX definition of a line at: # line. See POSIX definition of a line at:
if line.end == len(line.buffer) - 1 and line.buffer[line.end] == '\n': if ((line.end == len(line.buffer) - 1 and
line.buffer[line.end] == '\n') or
(line.end == len(line.buffer) - 2 and
line.buffer[line.end:line.end + 2] == '\r\n')):
# Allow the exception of the one-byte file containing '\n' # Allow the exception of the one-byte file containing '\n'
if line.end == 0: if line.end == 0:
return return

View File

@@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Greg Dubicki
#
# 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/>.
"""
Use this rule to prevent nodes with empty content, that implicitly result in
``null`` values.
.. rubric:: Options
* Use ``forbid-in-block-mappings`` to prevent empty values in block mappings.
* Use ``forbid-in-flow-mappings`` to prevent empty values in flow mappings.
.. rubric:: Examples
#. With ``empty-values: {forbid-in-block-mappings: true}``
the following code snippets would **PASS**:
::
some-mapping:
sub-element: correctly indented
::
explicitly-null: null
the following code snippets would **FAIL**:
::
some-mapping:
sub-element: incorrectly indented
::
implicitly-null:
#. With ``empty-values: {forbid-in-flow-mappings: true}``
the following code snippet would **PASS**:
::
{prop: null}
{a: 1, b: 2, c: 3}
the following code snippets would **FAIL**:
::
{prop: }
::
{a: 1, b:, c: 3}
"""
import yaml
from yamllint.linter import LintProblem
ID = 'empty-values'
TYPE = 'token'
CONF = {'forbid-in-block-mappings': bool,
'forbid-in-flow-mappings': bool}
DEFAULT = {'forbid-in-block-mappings': True,
'forbid-in-flow-mappings': True}
def check(conf, token, prev, next, nextnext, context):
if conf['forbid-in-block-mappings']:
if isinstance(token, yaml.ValueToken) and isinstance(next, (
yaml.KeyToken, yaml.BlockEndToken)):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'empty value in block mapping')
if conf['forbid-in-flow-mappings']:
if isinstance(token, yaml.ValueToken) and isinstance(next, (
yaml.FlowEntryToken, yaml.FlowMappingEndToken)):
yield LintProblem(token.start_mark.line + 1,
token.end_mark.column + 1,
'empty value in flow mapping')

View File

@@ -76,6 +76,7 @@ from yamllint.rules.common import spaces_after
ID = 'hyphens' ID = 'hyphens'
TYPE = 'token' TYPE = 'token'
CONF = {'max-spaces-after': int} CONF = {'max-spaces-after': int}
DEFAULT = {'max-spaces-after': 1}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):

View File

@@ -25,12 +25,12 @@ Use this rule to control the indentation.
same within the file. same within the file.
* ``indent-sequences`` defines whether block sequences should be indented or * ``indent-sequences`` defines whether block sequences should be indented or
not (when in a mapping, this indentation is not mandatory -- some people not (when in a mapping, this indentation is not mandatory -- some people
perceive the ``-`` as part of the indentation). Possible values: ``yes``, perceive the ``-`` as part of the indentation). Possible values: ``true``,
``no``, ``whatever`` and ``consistent``. ``consistent`` requires either all ``false``, ``whatever`` and ``consistent``. ``consistent`` requires either
block sequences to be indented, or none to be. ``whatever`` means either all block sequences to be indented, or none to be. ``whatever`` means either
indenting or not indenting individual block sequences is OK. indenting or not indenting individual block sequences is OK.
* ``check-multi-line-strings`` defines whether to lint indentation in * ``check-multi-line-strings`` defines whether to lint indentation in
multi-line strings. Set to ``yes`` to enable, ``no`` to disable. multi-line strings. Set to ``true`` to enable, ``false`` to disable.
.. rubric:: Examples .. rubric:: Examples
@@ -99,7 +99,7 @@ Use this rule to control the indentation.
Russian: Russian:
dolls dolls
#. With ``indentation: {spaces: 2, indent-sequences: no}`` #. With ``indentation: {spaces: 2, indent-sequences: false}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
:: ::
@@ -152,7 +152,7 @@ Use this rule to control the indentation.
- spaghetti - spaghetti
- sauce - sauce
#. With ``indentation: {spaces: 4, check-multi-line-strings: yes}`` #. With ``indentation: {spaces: 4, check-multi-line-strings: true}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
:: ::
@@ -193,7 +193,7 @@ Use this rule to control the indentation.
import yaml import yaml
from yamllint.linter import LintProblem from yamllint.linter import LintProblem
from yamllint.rules.common import is_explicit_key, get_real_end_line from yamllint.rules.common import get_real_end_line, is_explicit_key
ID = 'indentation' ID = 'indentation'
@@ -201,6 +201,9 @@ TYPE = 'token'
CONF = {'spaces': (int, 'consistent'), CONF = {'spaces': (int, 'consistent'),
'indent-sequences': (bool, 'whatever', 'consistent'), 'indent-sequences': (bool, 'whatever', 'consistent'),
'check-multi-line-strings': bool} 'check-multi-line-strings': bool}
DEFAULT = {'spaces': 'consistent',
'indent-sequences': True,
'check-multi-line-strings': False}
ROOT, B_MAP, F_MAP, B_SEQ, F_SEQ, B_ENT, KEY, VAL = range(8) ROOT, B_MAP, F_MAP, B_SEQ, F_SEQ, B_ENT, KEY, VAL = range(8)
labels = ('ROOT', 'B_MAP', 'F_MAP', 'B_SEQ', 'F_SEQ', 'B_ENT', 'KEY', 'VAL') labels = ('ROOT', 'B_MAP', 'F_MAP', 'B_SEQ', 'F_SEQ', 'B_ENT', 'KEY', 'VAL')
@@ -224,7 +227,7 @@ def check_scalar_indentation(conf, token, context):
def compute_expected_indent(found_indent): def compute_expected_indent(found_indent):
def detect_indent(base_indent): def detect_indent(base_indent):
if type(context['spaces']) is not int: if not isinstance(context['spaces'], int):
context['spaces'] = found_indent - base_indent context['spaces'] = found_indent - base_indent
return base_indent + context['spaces'] return base_indent + context['spaces']
@@ -312,7 +315,7 @@ def _check(conf, token, prev, next, nextnext, context):
token.start_mark.line + 1 > context['cur_line']) token.start_mark.line + 1 > context['cur_line'])
def detect_indent(base_indent, next): def detect_indent(base_indent, next):
if type(context['spaces']) is not int: if not isinstance(context['spaces'], int):
context['spaces'] = next.start_mark.column - base_indent context['spaces'] = next.start_mark.column - base_indent
return base_indent + context['spaces'] return base_indent + context['spaces']
@@ -399,6 +402,10 @@ def _check(conf, token, prev, next, nextnext, context):
# - item 1 # - item 1
# - item 2 # - item 2
indent = next.start_mark.column indent = next.start_mark.column
elif next.start_mark.column == token.start_mark.column:
# -
# key: value
indent = next.start_mark.column
else: else:
# - # -
# item 1 # item 1
@@ -469,7 +476,19 @@ def _check(conf, token, prev, next, nextnext, context):
if context['indent-sequences'] is False: if context['indent-sequences'] is False:
indent = context['stack'][-1].indent indent = context['stack'][-1].indent
elif context['indent-sequences'] is True: elif context['indent-sequences'] is True:
indent = detect_indent(context['stack'][-1].indent, next) if (context['spaces'] == 'consistent' and
next.start_mark.column -
context['stack'][-1].indent == 0):
# In this case, the block sequence item is not indented
# (while it should be), but we don't know yet the
# indentation it should have (because `spaces` is
# `consistent` and its value has not been computed yet
# -- this is probably the beginning of the document).
# So we choose an arbitrary value (2).
indent = 2
else:
indent = detect_indent(context['stack'][-1].indent,
next)
else: # 'whatever' or 'consistent' else: # 'whatever' or 'consistent'
if next.start_mark.column == context['stack'][-1].indent: if next.start_mark.column == context['stack'][-1].indent:
# key: # key:

View File

@@ -61,7 +61,6 @@ from yamllint.linter import LintProblem
ID = 'key-duplicates' ID = 'key-duplicates'
TYPE = 'token' TYPE = 'token'
CONF = {}
MAP, SEQ = range(2) MAP, SEQ = range(2)
@@ -91,7 +90,9 @@ def check(conf, token, prev, next, nextnext, context):
# This check is done because KeyTokens can be found inside flow # This check is done because KeyTokens can be found inside flow
# sequences... strange, but allowed. # sequences... strange, but allowed.
if len(context['stack']) > 0 and context['stack'][-1].type == MAP: if len(context['stack']) > 0 and context['stack'][-1].type == MAP:
if next.value in context['stack'][-1].keys: if (next.value in context['stack'][-1].keys and
# `<<` is "merge key", see http://yaml.org/type/merge.html
next.value != '<<'):
yield LintProblem( yield LintProblem(
next.start_mark.line + 1, next.start_mark.column + 1, next.start_mark.line + 1, next.start_mark.column + 1,
'duplication of key "%s" in mapping' % next.value) 'duplication of key "%s" in mapping' % next.value)

View File

@@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 Johannes F. Knauf
#
# 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/>.
"""
Use this rule to enforce alphabetical ordering of keys in mappings. The sorting
order uses the Unicode code point number. As a result, the ordering is
case-sensitive and not accent-friendly (see examples below).
.. rubric:: Examples
#. With ``key-ordering: {}``
the following code snippet would **PASS**:
::
- key 1: v
key 2: val
key 3: value
- {a: 1, b: 2, c: 3}
- T-shirt: 1
T-shirts: 2
t-shirt: 3
t-shirts: 4
- hair: true
hais: true
haïr: true
haïssable: true
the following code snippet would **FAIL**:
::
- key 2: v
key 1: val
the following code snippet would **FAIL**:
::
- {b: 1, a: 2}
the following code snippet would **FAIL**:
::
- T-shirt: 1
t-shirt: 2
T-shirts: 3
t-shirts: 4
the following code snippet would **FAIL**:
::
- haïr: true
hais: true
"""
import yaml
from yamllint.linter import LintProblem
ID = 'key-ordering'
TYPE = 'token'
MAP, SEQ = range(2)
class Parent(object):
def __init__(self, type):
self.type = type
self.keys = []
def check(conf, token, prev, next, nextnext, context):
if 'stack' not in context:
context['stack'] = []
if isinstance(token, (yaml.BlockMappingStartToken,
yaml.FlowMappingStartToken)):
context['stack'].append(Parent(MAP))
elif isinstance(token, (yaml.BlockSequenceStartToken,
yaml.FlowSequenceStartToken)):
context['stack'].append(Parent(SEQ))
elif isinstance(token, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken)):
context['stack'].pop()
elif (isinstance(token, yaml.KeyToken) and
isinstance(next, yaml.ScalarToken)):
# This check is done because KeyTokens can be found inside flow
# sequences... strange, but allowed.
if len(context['stack']) > 0 and context['stack'][-1].type == MAP:
if any(next.value < key for key in context['stack'][-1].keys):
yield LintProblem(
next.start_mark.line + 1, next.start_mark.column + 1,
'wrong ordering of key "%s" in mapping' % next.value)
else:
context['stack'][-1].keys.append(next.value)

View File

@@ -17,12 +17,16 @@
""" """
Use this rule to set a limit to lines length. Use this rule to set a limit to lines length.
Note: with Python 2, the ``line-length`` rule may not work properly with
unicode characters because of the way strings are represented in bytes. We
recommend running yamllint with Python 3.
.. rubric:: Options .. rubric:: Options
* ``max`` defines the maximal (inclusive) length of lines. * ``max`` defines the maximal (inclusive) length of lines.
* ``allow-non-breakable-words`` is used to allow non breakable words (without * ``allow-non-breakable-words`` is used to allow non breakable words (without
spaces inside) to overflow the limit. This is useful for long URLs, for spaces inside) to overflow the limit. This is useful for long URLs, for
instance. Use ``yes`` to allow, ``no`` to forbid. instance. Use ``true`` to allow, ``false`` to forbid.
* ``allow-non-breakable-inline-mappings`` implies ``allow-non-breakable-words`` * ``allow-non-breakable-inline-mappings`` implies ``allow-non-breakable-words``
and extends it to also allow non-breakable words in inline mappings. and extends it to also allow non-breakable words in inline mappings.
@@ -44,7 +48,7 @@ Use this rule to set a limit to lines length.
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. tempor incididunt ut labore et dolore magna aliqua.
#. With ``line-length: {max: 60, allow-non-breakable-words: yes}`` #. With ``line-length: {max: 60, allow-non-breakable-words: true}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
:: ::
@@ -68,15 +72,15 @@ Use this rule to set a limit to lines length.
- foobar: http://localhost/very/very/very/very/very/very/very/very/long/url - foobar: http://localhost/very/very/very/very/very/very/very/very/long/url
#. With ``line-length: {max: 60, allow-non-breakable-words: yes, #. With ``line-length: {max: 60, allow-non-breakable-words: true,
allow-non-breakable-inline-mappings: yes}`` allow-non-breakable-inline-mappings: true}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
:: ::
- foobar: http://localhost/very/very/very/very/very/very/very/very/long/url - foobar: http://localhost/very/very/very/very/very/very/very/very/long/url
#. With ``line-length: {max: 60, allow-non-breakable-words: no}`` #. With ``line-length: {max: 60, allow-non-breakable-words: false}``
the following code snippet would **FAIL**: the following code snippet would **FAIL**:
:: ::
@@ -98,6 +102,9 @@ TYPE = 'line'
CONF = {'max': int, CONF = {'max': int,
'allow-non-breakable-words': bool, 'allow-non-breakable-words': bool,
'allow-non-breakable-inline-mappings': bool} 'allow-non-breakable-inline-mappings': bool}
DEFAULT = {'max': 80,
'allow-non-breakable-words': True,
'allow-non-breakable-inline-mappings': False}
def check_inline_mapping(line): def check_inline_mapping(line):

View File

@@ -30,15 +30,17 @@ from yamllint.linter import LintProblem
ID = 'new-lines' ID = 'new-lines'
TYPE = 'line' TYPE = 'line'
CONF = {'type': ('unix', 'dos')} CONF = {'type': ('unix', 'dos')}
DEFAULT = {'type': 'unix'}
def check(conf, line): def check(conf, line):
if line.start == 0 and len(line.buffer) > line.end: if line.start == 0 and len(line.buffer) > line.end:
if conf['type'] == 'dos': if conf['type'] == 'dos':
if line.buffer[line.end - 1:line.end + 1] != '\r\n': if (line.end + 2 > len(line.buffer) or
line.buffer[line.end:line.end + 2] != '\r\n'):
yield LintProblem(1, line.end - line.start + 1, yield LintProblem(1, line.end - line.start + 1,
'wrong new line character: expected \\r\\n') 'wrong new line character: expected \\r\\n')
else: else:
if line.end > 0 and line.buffer[line.end - 1] == '\r': if line.buffer[line.end] == '\r':
yield LintProblem(1, line.end - line.start, yield LintProblem(1, line.end - line.start + 1,
'wrong new line character: expected \\n') 'wrong new line character: expected \\n')

View File

@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2017 ScienJus
#
# 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/>.
"""
Use this rule to prevent values with octal numbers. In YAML, numbers that
start with ``0`` are interpreted as octal, but this is not always wanted.
For instance ``010`` is the city code of Beijing, and should not be
converted to ``8``.
.. rubric:: Examples
#. With ``octal-values: {forbid-implicit-octal: true}``
the following code snippets would **PASS**:
::
user:
city-code: '010'
the following code snippets would **PASS**:
::
user:
city-code: 010,021
the following code snippets would **FAIL**:
::
user:
city-code: 010
#. With ``octal-values: {forbid-explicit-octal: true}``
the following code snippets would **PASS**:
::
user:
city-code: '0o10'
the following code snippets would **FAIL**:
::
user:
city-code: 0o10
"""
import yaml
from yamllint.linter import LintProblem
ID = 'octal-values'
TYPE = 'token'
CONF = {'forbid-implicit-octal': bool,
'forbid-explicit-octal': bool}
DEFAULT = {'forbid-implicit-octal': True,
'forbid-explicit-octal': True}
def check(conf, token, prev, next, nextnext, context):
if prev and isinstance(prev, yaml.tokens.TagToken):
return
if conf['forbid-implicit-octal']:
if isinstance(token, yaml.tokens.ScalarToken):
if not token.style:
val = token.value
if val.isdigit() and len(val) > 1 and val[0] == '0':
yield LintProblem(
token.start_mark.line + 1, token.end_mark.column + 1,
'forbidden implicit octal value "%s"' %
token.value)
if conf['forbid-explicit-octal']:
if isinstance(token, yaml.tokens.ScalarToken):
if not token.style:
val = token.value
if len(val) > 2 and val[:2] == '0o' and val[2:].isdigit():
yield LintProblem(
token.start_mark.line + 1, token.end_mark.column + 1,
'forbidden explicit octal value "%s"' %
token.value)

View File

@@ -0,0 +1,230 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2018 ClearScore
#
# 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/>.
"""
Use this rule to forbid any string values that are not quoted, or to prevent
quoted strings without needing it. You can also enforce the type of the quote
used.
.. rubric:: Options
* ``quote-type`` defines allowed quotes: ``single``, ``double`` or ``any``
(default).
* ``required`` defines whether using quotes in string values is required
(``true``, default) or not (``false``), or only allowed when really needed
(``only-when-needed``).
* ``extra-required`` is a list of PCRE regexes to force string values to be
quoted, if they match any regex. This option can only be used with
``required: false`` and ``required: only-when-needed``.
* ``extra-allowed`` is a list of PCRE regexes to allow quoted string values,
even if ``required: only-when-needed`` is set.
**Note**: Multi-line strings (with ``|`` or ``>``) will not be checked.
.. rubric:: Examples
#. With ``quoted-strings: {quote-type: any, required: true}``
the following code snippet would **PASS**:
::
foo: "bar"
bar: 'foo'
number: 123
boolean: true
the following code snippet would **FAIL**:
::
foo: bar
#. With ``quoted-strings: {quote-type: single, required: only-when-needed}``
the following code snippet would **PASS**:
::
foo: bar
bar: foo
not_number: '123'
not_boolean: 'true'
not_comment: '# comment'
not_list: '[1, 2, 3]'
not_map: '{a: 1, b: 2}'
the following code snippet would **FAIL**:
::
foo: 'bar'
#. With ``quoted-strings: {required: false, extra-required: [^http://,
^ftp://]}``
the following code snippet would **PASS**:
::
- localhost
- "localhost"
- "http://localhost"
- "ftp://localhost"
the following code snippet would **FAIL**:
::
- http://localhost
- ftp://localhost
#. With ``quoted-strings: {required: only-when-needed, extra-allowed:
[^http://, ^ftp://], extra-required: [QUOTED]}``
the following code snippet would **PASS**:
::
- localhost
- "http://localhost"
- "ftp://localhost"
- "this is a string that needs to be QUOTED"
the following code snippet would **FAIL**:
::
- "localhost"
- this is a string that needs to be QUOTED
"""
import re
import yaml
from yamllint.linter import LintProblem
ID = 'quoted-strings'
TYPE = 'token'
CONF = {'quote-type': ('any', 'single', 'double'),
'required': (True, False, 'only-when-needed'),
'extra-required': [str],
'extra-allowed': [str]}
DEFAULT = {'quote-type': 'any',
'required': True,
'extra-required': [],
'extra-allowed': []}
def VALIDATE(conf):
if conf['required'] is True and len(conf['extra-allowed']) > 0:
return 'cannot use both "required: true" and "extra-allowed"'
if conf['required'] is True and len(conf['extra-required']) > 0:
return 'cannot use both "required: true" and "extra-required"'
if conf['required'] is False and len(conf['extra-allowed']) > 0:
return 'cannot use both "required: false" and "extra-allowed"'
DEFAULT_SCALAR_TAG = u'tag:yaml.org,2002:str'
def _quote_match(quote_type, token_style):
return ((quote_type == 'any') or
(quote_type == 'single' and token_style == "'") or
(quote_type == 'double' and token_style == '"'))
def _quotes_are_needed(string):
loader = yaml.BaseLoader('key: ' + string)
# Remove the 5 first tokens corresponding to 'key: ' (StreamStartToken,
# BlockMappingStartToken, KeyToken, ScalarToken(value=key), ValueToken)
for _ in range(5):
loader.get_token()
try:
a, b = loader.get_token(), loader.get_token()
if (isinstance(a, yaml.ScalarToken) and a.style is None and
isinstance(b, yaml.BlockEndToken)):
return False
return True
except yaml.scanner.ScannerError:
return True
def check(conf, token, prev, next, nextnext, context):
if not (isinstance(token, yaml.tokens.ScalarToken) and
isinstance(prev, (yaml.BlockEntryToken, yaml.FlowEntryToken,
yaml.FlowSequenceStartToken, yaml.TagToken,
yaml.ValueToken))):
return
# Ignore explicit types, e.g. !!str testtest or !!int 42
if (prev and isinstance(prev, yaml.tokens.TagToken) and
prev.value[0] == '!!'):
return
# Ignore numbers, booleans, etc.
resolver = yaml.resolver.Resolver()
tag = resolver.resolve(yaml.nodes.ScalarNode, token.value, (True, False))
if token.plain and tag != DEFAULT_SCALAR_TAG:
return
# Ignore multi-line strings
if (not token.plain) and (token.style == "|" or token.style == ">"):
return
quote_type = conf['quote-type']
msg = None
if conf['required'] is True:
# Quotes are mandatory and need to match config
if token.style is None or not _quote_match(quote_type, token.style):
msg = "string value is not quoted with %s quotes" % quote_type
elif conf['required'] is False:
# Quotes are not mandatory but when used need to match config
if token.style and not _quote_match(quote_type, token.style):
msg = "string value is not quoted with %s quotes" % quote_type
elif not token.style:
is_extra_required = any(re.search(r, token.value)
for r in conf['extra-required'])
if is_extra_required:
msg = "string value is not quoted"
elif conf['required'] == 'only-when-needed':
# Quotes are not strictly needed here
if (token.style and tag == DEFAULT_SCALAR_TAG and token.value and
not _quotes_are_needed(token.value)):
is_extra_required = any(re.search(r, token.value)
for r in conf['extra-required'])
is_extra_allowed = any(re.search(r, token.value)
for r in conf['extra-allowed'])
if not (is_extra_required or is_extra_allowed):
msg = "string value is redundantly quoted with %s quotes" % (
quote_type)
# But when used need to match config
elif token.style and not _quote_match(quote_type, token.style):
msg = "string value is not quoted with %s quotes" % quote_type
elif not token.style:
is_extra_required = len(conf['extra-required']) and any(
re.search(r, token.value) for r in conf['extra-required'])
if is_extra_required:
msg = "string value is not quoted"
if msg is not None:
yield LintProblem(
token.start_mark.line + 1,
token.start_mark.column + 1,
msg)

View File

@@ -15,11 +15,24 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
Use this rule to forbid truthy values that are not quoted nor explicitly typed. Use this rule to forbid non-explictly typed truthy values other than allowed
ones (by default: ``true`` and ``false``), for example ``YES`` or ``off``.
This would prevent YAML parsers to tranform ``[yes, FALSE, Off]`` into ``[true, This can be useful to prevent surprises from YAML parsers transforming
false, false]`` or ``{y: 1, yes: 2, on: 3, true: 4, True: 5}`` into ``{y: 1, ``[yes, FALSE, Off]`` into ``[true, false, false]`` or
true: 5}``. ``{y: 1, yes: 2, on: 3, true: 4, True: 5}`` into ``{y: 1, true: 5}``.
.. rubric:: Options
* ``allowed-values`` defines the list of truthy values which will be ignored
during linting. The default is ``['true', 'false']``, but can be changed to
any list containing: ``'TRUE'``, ``'True'``, ``'true'``, ``'FALSE'``,
``'False'``, ``'false'``, ``'YES'``, ``'Yes'``, ``'yes'``, ``'NO'``,
``'No'``, ``'no'``, ``'ON'``, ``'On'``, ``'on'``, ``'OFF'``, ``'Off'``,
``'off'``.
* ``check-keys`` disables verification for keys in mappings. By default,
``truthy`` rule applies to both keys and values. Set this option to ``false``
to prevent this.
.. rubric:: Examples .. rubric:: Examples
@@ -34,8 +47,7 @@ true: 5}``.
"yes": 1 "yes": 1
"on": 2 "on": 2
"true": 3 "True": 3
"True": 4
explicit: explicit:
string1: !!str True string1: !!str True
@@ -58,36 +70,80 @@ true: 5}``.
object: {True: 1, 1: True} object: {True: 1, 1: True}
the following code snippet would **FAIL**: the following code snippet would **FAIL**:
::
yes: 1
on: 2
True: 3
#. With ``truthy: {allowed-values: ["yes", "no"]}``
the following code snippet would **PASS**:
::
- yes
- no
- "true"
- 'false'
- foo
- bar
the following code snippet would **FAIL**:
::
- true
- false
- on
- off
#. With ``truthy: {check-keys: false}``
the following code snippet would **PASS**:
:: ::
yes: 1 yes: 1
on: 2 on: 2
true: 3 true: 3
True: 4
the following code snippet would **FAIL**:
::
yes: Yes
on: On
true: True
""" """
import yaml import yaml
from yamllint.linter import LintProblem from yamllint.linter import LintProblem
ID = 'truthy'
TYPE = 'token'
CONF = {}
TRUTHY = ['YES', 'Yes', 'yes', TRUTHY = ['YES', 'Yes', 'yes',
'NO', 'No', 'no', 'NO', 'No', 'no',
'TRUE', 'True', # 'true' is a boolean 'TRUE', 'True', 'true',
'FALSE', 'False', # 'false' is a boolean 'FALSE', 'False', 'false',
'ON', 'On', 'on', 'ON', 'On', 'on',
'OFF', 'Off', 'off'] 'OFF', 'Off', 'off']
ID = 'truthy'
TYPE = 'token'
CONF = {'allowed-values': list(TRUTHY), 'check-keys': bool}
DEFAULT = {'allowed-values': ['true', 'false'], 'check-keys': True}
def check(conf, token, prev, next, nextnext, context): def check(conf, token, prev, next, nextnext, context):
if prev and isinstance(prev, yaml.tokens.TagToken): if prev and isinstance(prev, yaml.tokens.TagToken):
return return
if (not conf['check-keys'] and isinstance(prev, yaml.tokens.KeyToken) and
isinstance(token, yaml.tokens.ScalarToken)):
return
if isinstance(token, yaml.tokens.ScalarToken): if isinstance(token, yaml.tokens.ScalarToken):
if token.value in TRUTHY and token.style is None: if (token.value in (set(TRUTHY) - set(conf['allowed-values'])) and
token.style is None):
yield LintProblem(token.start_mark.line + 1, yield LintProblem(token.start_mark.line + 1,
token.start_mark.column + 1, token.start_mark.column + 1,
"truthy value is not quoted") "truthy value should be one of [" +
", ".join(sorted(conf['allowed-values'])) + "]")