Compare commits

..

149 Commits

Author SHA1 Message Date
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é
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
Adrien Vergé
559ad5574b yamllint version 1.5.0 2016-10-08 11:56:46 +02:00
Adrien Vergé
adcb2d2953 Merge pull request #24 from adamchainz/dont_package_tests
setup.py - don't distribute tests
2016-10-08 11:54:46 +02:00
Adam Chainz
e948509fe5 setup.py - don't distribute tests
Found them installed in my `site-packages`, importable as `import tests` 😱

Tested with:

```
In [2]: find_packages()
Out[2]: ['tests', 'yamllint', 'tests.rules', 'yamllint.rules']

In [3]: find_packages(exclude=['tests', 'tests.*'])
Out[3]: ['yamllint', 'yamllint.rules']
```
2016-10-07 10:52:48 +01:00
Adrien Vergé
6dae8f5b6e feat(truthy): Allow explicit types
With this change, we don't require quotes for truthy values that are
explicitly typed. For instance, the following examples are all
considered valid:

    string1: !!str True
    string2: !!str yes
    string3: !!str off
    encoded: !!binary |
               True
               OFF
               pad==  # this decodes as 'N\xbb\x9e8Qii'
    boolean1: !!bool true
    boolean2: !!bool "false"
    boolean3: !!bool FALSE
    boolean4: !!bool True
    boolean5: !!bool off
    boolean6: !!bool NO
2016-10-02 08:15:53 +02:00
Adrien Vergé
073462a87d docs(rules): Fix missing truthy rule in index 2016-10-01 10:00:23 +02:00
Adrien Vergé
4b9ba9e201 docs(truthy): Enhance rule documentation 2016-10-01 10:00:23 +02:00
Peter Ericson
5294ff5552 truthy: Add tests for explicit booleans
From @adrienverge
2016-10-01 09:04:37 +02:00
Peter Ericson
1f472bc144 Add rule: truthy, to forbid truthy values that are not quoted 2016-10-01 09:03:59 +02:00
Adrien Vergé
c163135ee5 yamllint version 1.4.1 2016-09-27 09:46:40 +02:00
Adrien Vergé
f656cf42d2 fix(line-length): Wrap token scanning securely
With `allow-non-breakable-inline-mappings` enabled, every long line is
passed through `loader.peek_token()`. Even lines that are not valid
YAML. For this reason, this code must be wrapped in a `try`/`except`
block.

Closes: #21
2016-09-27 09:27:43 +02:00
Adrien Vergé
9b72a2d29a Merge branch 'adamchainz-readthedocs.io' 2016-09-21 10:47:01 +02:00
Adam Chainz
d7c17c7e7c Doc: Convert readthedocs links from .org to .io
As per [their blog post of the 27th
April](https://blog.readthedocs.com/securing-subdomains/) ‘Securing
subdomains’:

> Starting today, Read the Docs will start hosting projects from
> subdomains on the domain readthedocs.io, instead of on
> readthedocs.org. This change addresses some security concerns around
> site cookies while hosting user generated data on the same domain as
> our dashboard.

Test Plan: Manually visited all the links I’ve modified.
2016-09-21 10:36:00 +02:00
Adrien Vergé
60b72daad4 yamllint version 1.4.0 2016-09-19 13:53:03 +02:00
Adrien Vergé
773bb8a648 Merge pull request #17 from allanlewis/improve-unbreakable
line_length: Allow mapping values with long unbreakable lines
2016-09-19 13:51:44 +02:00
Adrien Vergé
d3cd8ba332 line-length: Generalize ...-inline-mappings for corner cases
This commit refactors the `allow-non-breakable-inline-mappings` logic to
use YAML tokens and avoid crashes or erroneous reports on cases like:

```yaml
- {a: "http://localhost/very/very/very/very/very/very/long/url"
   }
```

```yaml
dict:
  {a: long long long long long long long, b: nospace}
```

```yaml
- long_line: http://localhost/very/very/long/url
```

```yaml
long_line: and+some+space+at+the+end       <-- extra spaces
```

For reference see:
https://github.com/adrienverge/yamllint/pull/17#issuecomment-247805799
2016-09-19 12:39:52 +01:00
Allan Lewis
e56a7c788c line_length: Extract inline logic to new config option
This commit extracts the inline mappings logic defined in the previous
commit to a separate config option, as suggested by @adrienverge. I'll
squash this into the previous commit if the change is accepted. (I named
the option slightly differently to what was suggested as I think my
proposal reads better without consulting the docs: I'd be happy to
reconsider this.)
2016-09-19 12:39:52 +01:00
Allan Lewis
d017631aff line_length: Allow mapping values with long unbreakable lines 2016-09-12 16:31:58 +01:00
Adrien Vergé
5b98cd2053 feat(comments): Allow comments with multiple hash chars
This change make the `comments` rule accept comments that start with
multiple pound signs, e.g.:

    ##############################
    ## This is some documentation

Closes: #12
2016-08-12 11:58:57 +02:00
Adrien Vergé
82dd7dbf16 Merge pull request #16 from adrienverge/coloured_output_on_tty
feat(cli): Colour output only on TTY
2016-08-12 11:58:00 +02:00
Adrien Vergé
4533b8ae49 doc(config): Show relaxed conf contents
Closes: #15
2016-08-12 11:17:18 +02:00
Adrien Vergé
a2c68fdf9b feat(cli): Colour output only on TTY
When piping yamllint output to a file, "coloured" characters aren't
interpreted and pollute text formatting with glyphs like:

  �[4m./global.yaml�[0m
    �[2m1439:52�[0m   �[31merror�[0m    no new line character...

With this commit, stdout is checked: if it's a TTY then output is
coloured, otherwise output is simple text.

Closes: #14
2016-08-12 11:03:41 +02:00
Adrien Vergé
82ed191bc9 yamllint version 1.3.2 2016-06-28 12:06:58 +02:00
Adrien Vergé
92ff315fb4 Tests: Set proper LC_ALL when decoding UTF-8 is needed
Make sure the default localization conditions on the "test system"
support UTF-8 encoding.
2016-06-28 12:06:55 +02:00
Adrien Vergé
f4cebdc054 Tests: Run with LC_ALL=C for uniform tests
Use default (C) locale in all tests to make sure the localization
conditions are the same wherever tests are run.
2016-06-28 11:04:50 +02:00
Adrien Vergé
d174f9e3e3 yamllint version 1.3.1 2016-06-28 10:10:34 +02:00
Adrien Vergé
c8ba8f7e99 linter: Fix UnicodeError when parsing comments
And add tests when reading non-ASCII strings and comments (both from
Python strings and from files).

Fixes: #10
2016-06-28 09:58:23 +02:00
Adrien Vergé
63dd8313f8 yamllint version 1.3.0 2016-06-27 21:54:29 +02:00
Adrien Vergé
7be5867675 linter: Remove dead code
There is *always* a `Line` element at the end of file, even if the
newline character (`\n`) is missing.
2016-06-27 21:45:21 +02:00
Adrien Vergé
6061a2c4cc Rules: common: Remove dead code 2016-06-27 21:37:31 +02:00
Adrien Vergé
09118e417c Doc: Add license information on README page 2016-06-27 21:32:55 +02:00
Adrien Vergé
71b90ae208 Doc: Add new features in README 2016-06-27 21:32:55 +02:00
Adrien Vergé
8844855353 Doc: Remove old Debian install commands from README 2016-06-27 21:15:55 +02:00
Adrien Vergé
0eb310e102 Allow disabling yamllint checks using comments
Implement problem report disabling with comments in YAML source, for
instance:

    # The following mapping contains the same key twice,
    # but I know what I'm doing:
    key: value 1
    key: value 2  # yamllint disable-line rule:key-duplicates

or:

    # yamllint disable rule:colons
    - Lorem       : ipsum
      dolor       : sit amet,
      consectetur : adipiscing elit
    # yamllint enable rule:colons

Closes: #8
2016-06-27 17:53:23 +02:00
Adrien Vergé
cdd094220c parser: Add tests for Comment.is_inline() 2016-06-27 17:47:13 +02:00
Adrien Vergé
7a7d98c96a parser: Iterate over lines + tokens + comments
Instead of iterating over lines and tokens (and find comments between
tokens in the comment rules), add a new `Comment` type and set rules
with `type = 'comment'`.
2016-06-27 17:47:13 +02:00
Adrien Vergé
9f99f25db5 linter: Assert that _run() is called with a buffer 2016-06-25 13:50:24 +02:00
Adrien Vergé
8c839a20c2 Config: Detect user config using os.path.expanduser()
Instead of `$HOME`, since the former works when `$HOME` is not set.

[1]: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=828033#10
2016-06-24 18:41:45 +02:00
Adrien Vergé
8e6e851c5b yamllint version 1.2.2 2016-06-24 08:40:45 +02:00
Adrien Vergé
edd4cca02f Merge pull request #9 from michelebariani/master
Patch allow-non-breakable-words on '-'
2016-06-15 20:06:04 +02:00
Michele Bariani
867970258e Patch allow-non-breakable-words on '-' 2016-06-15 18:07:42 +02:00
Adrien Vergé
d0cb5998c4 Merge pull request #7 from jwilk/spelling
Fix typos
2016-05-13 16:19:58 +02:00
Jakub Wilk
a5c97220e7 Fix typos 2016-05-13 15:47:56 +02:00
Adrien Vergé
598e5e4370 Doc: Fix typo on configuration page intro 2016-04-21 22:39:46 +02:00
Adrien Vergé
03076ee214 Doc: Add a pointer to rules on configuration page intro 2016-04-21 22:37:48 +02:00
Adrien Vergé
eabd349902 Config: Allow a user-global configuration file
Instead of just looking for `.yamllint` in the current working
directory, also look for `~/.config/yamllint/config` (using
`$XDG_CONFIG_HOME` or `$HOME`, see [1] and [2] for information).

[1]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-0.6.html
[2]: https://wiki.archlinux.org/index.php/XDG_Base_Directory_support

Closes: #6
2016-04-21 22:24:24 +02:00
Adrien Vergé
1f1757ced4 yamllint version 1.2.1 2016-03-25 13:55:19 +01:00
Adrien Vergé
59d5bffbec Tests: cli: Detect and handle the -d '' case 2016-03-25 13:46:19 +01:00
Adrien Vergé
53da21934d Tests: Add many cli.run test cases 2016-03-25 13:46:19 +01:00
Adrien Vergé
0c36d0175c cli: Print EnvironmentErrors on stderr
Errors such as "no such file or directory" should not be printed on
standard output.
2016-03-25 13:46:19 +01:00
Adrien Vergé
20545febe5 CI: Lint project's *.yaml files as well as *.yml 2016-03-25 13:46:19 +01:00
Adrien Vergé
88ebcbbb93 Tests: Test cli.find_files_recursively 2016-03-25 13:46:19 +01:00
Adrien Vergé
08615ec4f8 Tests: config: Check the non-valid-yaml-config case 2016-03-25 13:46:19 +01:00
Adrien Vergé
29aceb430a Tests: indentation: Increase coverage 2016-03-25 13:46:19 +01:00
Adrien Vergé
159e29ea6a Rules: indentation: Remove non-existing case
A BlockMappingStartToken should always be followed by a KeyToken, on the
same line.
2016-03-25 13:46:19 +01:00
Adrien Vergé
f9198b7a9b Rules: indentation: Fix B_SEQ instead of B_ENT
For example in this case, the scalar's parent is a B_ENT (only its
grandparent is a B_SEQ):

    - >
        multi
        line
2016-03-25 13:46:19 +01:00
Adrien Vergé
44236077dd Merge pull request #4 from adrienverge/indentation-imbricated-flows
Indentation: imbricated flows
2016-03-25 13:45:05 +01:00
Adrien Vergé
76f47e91ca Rules: indentation: Handle imbricated flows correctly
The following source -- although not loadable by pyyaml -- is valid
YAML:

    {{key}}: value

This was processed badly by yamllint. The same for `[[value]]`,
`{{{{{moustaches}}}}}` or:

    {[val,
      {{key: val,
        key2}}]}

This patch corrects it and add corresponding test cases.

Related-to: #3
2016-03-22 14:34:07 +01:00
Adrien Vergé
f98bed1085 Rules: indentation: Do not crash on unexpected token
Previously, when the indentation rule blocked on an unexpected token,
the program crashed with something like:

    File "/usr/lib/python3/dist-packages/yamllint/rules/indentation.py",
    line 434, in check
      assert context['stack'][-1].type == KEY
    AssertionError

Instead, we prefer report the error as a regular `LintProblem` and
continue processing.

Fixes: #3
2016-03-22 14:34:07 +01:00
Adrien Vergé
a483524b63 Doc: Update installing section
Packages are now also available in Debian and Ubuntu.
2016-03-15 10:04:24 +01:00
Adrien Vergé
3a017a5a22 Doc: Update Neovim integration documentation
Since it has been merged into Neomake:
https://github.com/benekastah/neomake/commit/45dfc5
https://github.com/benekastah/neomake/pull/289
2016-03-10 08:59:31 +01:00
Adrien Vergé
bab8137e2b Update .gitignore 2016-03-08 09:48:17 +01:00
Adrien Vergé
41733fc7a5 Use '.yaml' extension as default, not '.yml'
As someone said [1] on the internet:

    Say ".yaml" not ".yml".
    This is not MS-DOS, and YML is a Yahoo XML dialect.

Similarly, we use '.json', not '.jsn'.

[1]: https://github.com/ceph/s3-tests/commit/e17c56a
2016-03-07 11:15:04 +01:00
Adrien Vergé
688858e639 Doc: Reference Fedora and Ubuntu packages 2016-03-07 11:05:29 +01:00
Adrien Vergé
dca3a54e63 yamllint version 1.2.0 2016-03-06 17:04:05 +01:00
Adrien Vergé
2dcfbd7e0d Conf: relaxed: Remove unneeded lines 2016-03-06 17:04:05 +01:00
Adrien Vergé
73d7a608e8 Conf: relaxed: Re-enable hyphens (in warning) 2016-03-06 17:04:05 +01:00
Adrien Vergé
1c0f164fbf Conf: relaxed: Set indentation's indent-sequences=consistent 2016-03-06 17:01:18 +01:00
Adrien Vergé
46e9108419 Rules: indentation: Add 'consistent' option for 'indent-sequences'
Using `indent-sequences: consistent` allows block sequences to be
indented or not to be, as long as it remains the same within the file.
2016-03-06 15:42:16 +01:00
Adrien Vergé
2f9e3cc71b Conf: relaxed: Set indentation to warning level 2016-03-06 08:26:09 +01:00
Adrien Vergé
b13a03815a Conf: default: Use spaces: consistent for indentation 2016-03-06 08:26:09 +01:00
Adrien Vergé
9a7eec34b1 Rules: indentation: Fix spaces: consitent with broken flows 2016-03-06 08:26:09 +01:00
Adrien Vergé
5b62548ece Tests: indentation: Use 'spaces: consistent' by default 2016-03-06 08:26:09 +01:00
Adrien Vergé
8fca8a7a33 Config: Allow 'enable' keyword for rules
In the same manner as 'disable', 'enable' allows setting a rule on
without worrying about its options.
2016-03-06 08:00:25 +01:00
Adrien Vergé
69ef9a7272 Conf: relaxed: Set max line-length back to 80
Because 80 has been the default for years. But keep it as a warning, not
an error.
2016-03-06 07:42:49 +01:00
Adrien Vergé
d8d1d92545 yamllint version 1.1.0 2016-03-04 17:03:38 +01:00
Adrien Vergé
7688567faa cli: Add the -d option to provide inline conf 2016-03-04 16:53:26 +01:00
Adrien Vergé
4e188f8801 Conf: Add a new pre-defined conf 'relaxed'
It is more tolerant than 'default'.
2016-03-04 16:50:40 +01:00
Adrien Vergé
5693b1dddf Rules: indentation: Add 'consistent' option for 'spaces'
Using `spaces: consistent` allows any number of spaces, as long as it
remains the same within the file.
2016-03-04 16:03:53 +01:00
Adrien Vergé
fa420499c7 Config: Allow types in multiple choices
For instance, allow rules with:

    CONF = {'choice': (int, 'hardcoded-string'),
            'string-or-bool': (str, bool)}
2016-03-04 16:03:46 +01:00
Adrien Vergé
adefe38a0d yamllint version 1.0.4 2016-03-04 12:48:31 +01:00
Adrien Vergé
7e11082353 Distribution: Restore spec examples in package_data
Put `tests/yaml-1.2-spec-examples/*` back in `setup.py`'s `package_data`
because they need to be installed when running `python setup.py build`,
so Debian packaging script `dh_auto_test -O--buildsystem=pybuild`
doesn't fail.

See also commit e6dc67f.
2016-03-04 12:33:56 +01:00
Adrien Vergé
29c1c60143 Tests: Use absolute path to spec examples 2016-03-04 12:15:26 +01:00
Adrien Vergé
b879e9a98f Distribution: Add LICENSE and README to manifest 2016-02-26 09:57:06 +01:00
Adrien Vergé
5956b20545 yamllint version 1.0.3 2016-02-25 14:48:13 +01:00
Adrien Vergé
10ad302e2f Tests: Explicit encoding for spec examples
YAML specification examples contain unusual characters, let's explicit
`encoding='utf-8'` to prevent bugs.
2016-02-25 10:44:05 +01:00
Adrien Vergé
73d9322813 linter: Test run on str, unicode, bytes and stream
Previously it was not tested, and broke on Python 2 `unicode` inputs.
2016-02-25 10:41:17 +01:00
Adrien Vergé
ca0ebe4583 yamllint version 1.0.2 2016-02-24 21:21:02 +01:00
Adrien Vergé
e6dc67fd0a Distribution: Add MANIFEST.in
`yamllint/conf/*.yml` remains in `setup.py`'s `package_data` because it
needs to be installed when running `pip install .`.

`docs/*` and `tests/yaml-1.2-spec-examples/*` just need to be packaged,
they can go in the manifest.
2016-02-24 21:18:48 +01:00
Adrien Vergé
611a560082 yamllint version 1.0.1 2016-02-19 19:39:52 +01:00
Adrien Vergé
83384fa4cf Doc: Fix man page redundant description 2016-02-19 19:34:20 +01:00
Adrien Vergé
3ab3784a75 cli: Remove shebang
A shebang is present at the beginning of file, it dates from the time
when `yamllint/cli.py` was `bin/yamllint`, i.e. an executable launcher.
Since this is not the case anymore (see `entry_points` section in
`setup.py`), let's remove it.
2016-02-19 19:17:49 +01:00
Adrien Vergé
2f75e92a66 Doc: Add a configuration example in README 2016-02-19 10:37:52 +01:00
Adrien Vergé
64caa95b6a yamllint version 1.0.0 2016-02-19 10:15:23 +01:00
Adrien Vergé
fff09fa2df Distribution: Ship example files from spec in sdist
Closes: #1
2016-02-19 10:14:59 +01:00
Adrien Vergé
316bee8c98 yamllint version 0.7.2 2016-02-05 11:28:15 +01:00
Adrien Vergé
6c8af97a40 Tests: unblacklist remaining spec examples
Since !!tags are now supported.
2016-02-05 11:14:37 +01:00
Adrien Vergé
647d84ff94 Rules: indentation: Handle tags 2016-02-05 11:13:44 +01:00
Adrien Vergé
8eb0d0ad74 Tests: unblacklist spec example 7.16
As is it supported -- it just lacks some indentation.
2016-02-05 09:52:09 +01:00
Adrien Vergé
4bc3d5a01c Rules: indentation: Handle anchors 2016-02-04 22:10:40 +01:00
Adrien Vergé
48c7d65c54 parser: Provide nextnext for token rules
Because the indentation rule sometimes needs to look two tokens forward
(in case of anchors for instance).
2016-02-04 22:10:40 +01:00
Adrien Vergé
62fa4cbe39 Tests: indentation: Test the indent stack
The "indentation stack" is iteratively built by the `check()` function
of the indentation rule. It is important, since everything in the rule
relies on it.

This patch adds tests to make sure the stack is correctly built for some
known structures.
2016-02-04 22:10:40 +01:00
Adrien Vergé
8d38d349ac Rules: indentation: Rewrite stack generation
"Indentation stack" generation was not done properly, hence did not work
in all cases. This commit does a cleaner rewriting.
2016-02-04 21:47:08 +01:00
Adrien Vergé
3f264806b9 yamllint version 0.7.1 2016-02-03 14:43:09 +01:00
Adrien Vergé
9a82b99d4b Rules: indentation: Fix multi-line flows
To detect this as correct indentations:

    top:
      rules: [
        {
          foo: 1
        },
        {
          foo: 2
          bar: [
            a, b, c
          ],
        },
      ]
2016-02-03 12:05:22 +01:00
Adrien Vergé
ba140ad42c Tests: Remove ghost character from YAML spec example 2016-02-01 23:27:49 +01:00
Adrien Vergé
0e04ee29e6 Doc: Update description 2016-02-01 23:03:25 +01:00
62 changed files with 4265 additions and 547 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
__pycache__ __pycache__
*.py[cod] *.py[cod]
/docs/_build /docs/_build
/dist
/yamllint.egg-info

View File

@@ -1,17 +1,20 @@
--- ---
language: python language: python
python: python:
- 2.6
- 2.7 - 2.7
- 3.3 - 3.3
- 3.4 - 3.4
- 3.5 - 3.5
- 3.6
- nightly - nightly
install: install:
- pip install pyyaml flake8 coveralls - pip install pyyaml flake8 flake8-import-order coveralls
- if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install unittest2; fi
- pip install . - pip install .
script: script:
- flake8 . - if [[ $TRAVIS_PYTHON_VERSION != 2.6 ]]; then flake8 .; fi
- yamllint $(git ls-files '*.yml') - yamllint --strict $(git ls-files '*.yaml' '*.yml')
- coverage run --source=yamllint setup.py test - coverage run --source=yamllint setup.py test
after_success: after_success:
coveralls coveralls

15
CHANGELOG.rst Normal file
View File

@@ -0,0 +1,15 @@
Changelog
=========
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

3
MANIFEST.in Normal file
View File

@@ -0,0 +1,3 @@
include LICENSE
include README.rst
include docs/*

View File

@@ -3,8 +3,9 @@ yamllint
A linter for YAML files. A linter for YAML files.
yamllint does not only check for syntax validity, but for common cosmetic yamllint does not only check for syntax validity, but for weirdnesses like key
conventions such as lines length, trailing spaces, indentation, etc. repetition and cosmetic problems such as lines length, trailing spaces,
indentation, etc.
.. image:: .. image::
https://travis-ci.org/adrienverge/yamllint.svg?branch=master https://travis-ci.org/adrienverge/yamllint.svg?branch=master
@@ -15,7 +16,7 @@ conventions such as lines length, trailing spaces, indentation, etc.
:target: https://coveralls.io/github/adrienverge/yamllint?branch=master :target: https://coveralls.io/github/adrienverge/yamllint?branch=master
:alt: Code coverage status :alt: Code coverage status
.. image:: https://readthedocs.org/projects/yamllint/badge/?version=latest .. image:: https://readthedocs.org/projects/yamllint/badge/?version=latest
:target: http://yamllint.readthedocs.org/en/latest/?badge=latest :target: https://yamllint.readthedocs.io/en/latest/?badge=latest
:alt: Documentation status :alt: Documentation status
Written in Python (compatible with Python 2 & 3). Written in Python (compatible with Python 2 & 3).
@@ -23,10 +24,10 @@ Written in Python (compatible with Python 2 & 3).
Documentation Documentation
------------- -------------
http://yamllint.readthedocs.org/ https://yamllint.readthedocs.io/
Short overview Overview
-------------- --------
Screenshot Screenshot
^^^^^^^^^^ ^^^^^^^^^^
@@ -37,6 +38,20 @@ Screenshot
Installation Installation
^^^^^^^^^^^^ ^^^^^^^^^^^^
On Fedora / CentOS:
.. code:: bash
sudo dnf install yamllint
On Debian 8+ / Ubuntu 16.04+:
.. code:: bash
sudo apt-get install yamllint
Alternatively using pip, the Python package manager:
.. code:: bash .. code:: bash
sudo pip install yamllint sudo pip install yamllint
@@ -56,10 +71,78 @@ Usage
.. code:: bash .. code:: bash
# Use a pre-defined lint configuration
yamllint -d relaxed file.yaml
# Use a custom lint configuration # Use a custom lint configuration
yamllint -c ~/myconfig file.yml yamllint -c /path/to/myconfig file-to-lint.yaml
.. code:: bash .. code:: bash
# Output a parsable format (for syntax checking in editors like Vim, emacs...) # Output a parsable format (for syntax checking in editors like Vim, emacs...)
yamllint -f parsable file.yml yamllint -f parsable file.yaml
`Read more in the complete documentation! <https://yamllint.readthedocs.io/>`_
Features
^^^^^^^^
Here is a yamllint configuration file example:
.. code:: yaml
extends: default
rules:
# 80 chars should be enough, but don't fail if a line is longer
line-length:
max: 80
level: warning
# don't bother me with this rule
indentation: disable
Within a YAML file, special comments can be used to disable checks for a single
line:
.. code:: yaml
This line is waaaaaaaaaay too long # yamllint disable-line
or for a whole block:
.. code:: yaml
# yamllint disable rule:colons
- Lorem : ipsum
dolor : sit amet,
consectetur : adipiscing elit
# 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/>`_
License
-------
`GPL version 3 <LICENSE>`_

View File

@@ -38,6 +38,5 @@ htmlhelp_basename = 'yamllintdoc'
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [
('index', 'yamllint', u'yamllint Documentation', ('index', 'yamllint', '', [u'Adrien Vergé'], 1)
[u'Adrien Vergé'], 1)
] ]

View File

@@ -1,27 +1,47 @@
Configuration Configuration
============= =============
yamllint uses a set of *rules* to check sources files for problems. Each rule is yamllint uses a set of :doc:`rules <rules>` to check source files for problems.
independent from the others, and can be enabled, disabled or tweaked. All these Each rule is independent from the others, and can be enabled, disabled or
settings can be gathered in a configuration file. tweaked. All these settings can be gathered in a configuration file.
To use a custom configuration file, either name it ``.yamllint`` in your working To use a custom configuration file, use the ``-c`` option:
directory, or use the ``-c`` option:
:: .. code:: bash
yamllint -c ~/myconfig file.yml yamllint -c /path/to/myconfig file-to-lint.yaml
If ``-c`` is not provided, yamllint will look for a configuration file in the
following locations (by order of preference):
- ``.yamllint`` in the current working directory
- ``$XDG_CONFIG_HOME/yamllint/config``
- ``~/.config/yamllint/config``
Finally if no config file is found, the default configuration is applied.
Default configuration Default configuration
--------------------- ---------------------
Unless told otherwise, yamllint uses its ``default`` configuration: Unless told otherwise, yamllint uses its ``default`` configuration:
.. literalinclude:: ../yamllint/conf/default.yml .. literalinclude:: ../yamllint/conf/default.yaml
:language: yaml :language: yaml
Details on rules can be found on :doc:`the rules page <rules>`. Details on rules can be found on :doc:`the rules page <rules>`.
There is another pre-defined configuration named ``relaxed``. As its name
suggests, it is more tolerant:
.. literalinclude:: ../yamllint/conf/relaxed.yaml
:language: yaml
It can be chosen using:
.. code:: bash
yamllint -d relaxed file.yml
Extending the default configuration Extending the default configuration
----------------------------------- -----------------------------------
@@ -50,7 +70,7 @@ strict on block sequences indentation:
extends: default extends: default
rules: rules:
# 80 should be enough, but don't fail if a line is longer # 80 chars should be enough, but don't fail if a line is longer
line-length: line-length:
max: 80 max: 80
level: warning level: warning
@@ -63,12 +83,88 @@ strict on block sequences indentation:
indentation: indentation:
indent-sequences: whatever indent-sequences: whatever
Custom configuration without a config file
------------------------------------------
It is possible -- although not recommended -- to pass custom configuration
options to yamllint with the ``-d`` (short for ``--config-data``) option.
Its content can either be the name of a pre-defined conf (example: ``default``
or ``relaxed``) or a serialized YAML object describing the configuration.
For instance:
.. code:: bash
yamllint -d "{extends: relaxed, rules: {line-length: {max: 120}}}" file.yaml
Errors and warnings 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 ``standard``
output format).
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 one or
``standard`` output format), but the exit code can be different. More precisely, 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
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

@@ -0,0 +1,75 @@
Disable with comments
=====================
Disabling checks for a specific line
------------------------------------
To prevent yamllint from reporting problems for a specific line, add a directive
comment (``# yamllint disable-line ...``) on that line, or on the line above.
For instance:
.. code-block:: yaml
# The following mapping contains the same key twice,
# but I know what I'm doing:
key: value 1
key: value 2 # yamllint disable-line rule:key-duplicates
- This line is waaaaaaaaaay too long but yamllint will not report anything about it. # yamllint disable-line rule:line-length
This line will be checked by yamllint.
or:
.. code-block:: yaml
# The following mapping contains the same key twice,
# but I know what I'm doing:
key: value 1
# yamllint disable-line rule:key-duplicates
key: value 2
# yamllint disable-line rule:line-length
- This line is waaaaaaaaaay too long but yamllint will not report anything about it.
This line will be checked by yamllint.
It is possible, although not recommend, to disabled **all** rules for a
specific line:
.. code-block:: yaml
# yamllint disable-line
- { all : rules ,are disabled for this line}
If you need to disable multiple rules, it is allowed to chain rules like this:
``# yamllint disable-line rule:hyphens rule:commas rule:indentation``.
Disabling checks for all (or part of) the file
----------------------------------------------
To prevent yamllint from reporting problems for the whole file, or for a block of
lines within the file, use ``# yamllint disable ...`` and ``# yamllint enable
...`` directive comments. For instance:
.. code-block:: yaml
# yamllint disable rule:colons
- Lorem : ipsum
dolor : sit amet,
consectetur : adipiscing elit
# yamllint enable rule:colons
- rest of the document...
It is possible, although not recommend, to disabled **all** rules:
.. code-block:: yaml
# yamllint disable
- Lorem :
ipsum:
dolor : [ sit,amet]
- consectetur : adipiscing elit
# yamllint enable
If you need to disable multiple rules, it is allowed to chain rules like this:
``# yamllint disable rule:hyphens rule:commas rule:indentation``.

View File

@@ -1,10 +1,7 @@
yamllint documentation yamllint documentation
====================== ======================
A linter for YAML files. .. automodule:: yamllint
yamllint does not only check for syntax validity, but for common cosmetic
conventions such as lines length, trailing spaces, indentation, etc.
Screenshot Screenshot
---------- ----------
@@ -26,5 +23,6 @@ Table of contents
quickstart quickstart
configuration configuration
rules rules
disable_with_comments
development development
text_editors text_editors

View File

@@ -4,16 +4,34 @@ Quickstart
Installing yamllint Installing yamllint
------------------- -------------------
First, install yamllint. The easiest way is to use pip, the Python package On Fedora / CentOS:
manager:
:: .. code:: bash
sudo dnf install yamllint
On Debian 8+ / Ubuntu 16.04+:
.. code:: bash
sudo apt-get install yamllint
On older Debian / Ubuntu versions:
.. code:: bash
sudo add-apt-repository -y ppa:adrienverge/ppa && sudo apt-get update
sudo apt-get install yamllint
Alternatively using pip, the Python package manager:
.. code:: bash
sudo pip install yamllint sudo pip install 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
python setup.py sdist python setup.py sdist
sudo pip install dist/yamllint-*.tar.gz sudo pip install dist/yamllint-*.tar.gz
@@ -23,13 +41,13 @@ Running yamllint
Basic usage: Basic usage:
:: .. code:: bash
yamllint file.yml other-file.yaml yamllint file.yml other-file.yaml
You can also lint all YAML files in a whole directory: You can also lint all YAML files in a whole directory:
:: .. code:: bash
yamllint . yamllint .
@@ -65,9 +83,9 @@ If you have a custom linting configuration file (see :doc:`how to configure
yamllint <configuration>`), it can be passed to yamllint using the ``-c`` yamllint <configuration>`), it can be passed to yamllint using the ``-c``
option: option:
:: .. code:: bash
yamllint -c ~/myconfig file.yml yamllint -c ~/myconfig file.yaml
.. note:: .. note::

View File

@@ -93,3 +93,8 @@ trailing-spaces
--------------- ---------------
.. automodule:: yamllint.rules.trailing_spaces .. automodule:: yamllint.rules.trailing_spaces
truthy
---------------
.. automodule:: yamllint.rules.truthy

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``:
:: ::
@@ -20,16 +24,14 @@ Neovim
------ ------
Assuming that the `neomake <https://github.com/benekastah/neomake>`_ plugin is Assuming that the `neomake <https://github.com/benekastah/neomake>`_ plugin is
installed, add to your ``.config/nvim/init.vim``: installed, yamllint is supported by default. It is automatically enabled when
editing YAML files.
:: Emacs
-----
if executable('yamllint') If you are `flycheck <https://github.com/flycheck/flycheck>`_ user, you can use
let g:neomake_yaml_yamllint_maker = { `flycheck-yamllint <https://github.com/krzysztof-magosa/flycheck-yamllint>`_ integration.
\ 'args': ['-f', 'parsable'],
\ 'errorformat': '%E%f:%l:%c: [error] %m,%W%f:%l:%c: [warning] %m' }
let g:neomake_yaml_enabled_makers = ['yamllint']
endif
Other text editors Other text editors
------------------ ------------------

11
hooks.yaml Normal file
View File

@@ -0,0 +1,11 @@
---
# For use with pre-commit.
# See usage instructions at http://pre-commit.com
- id: yamllint
name: yamllint
description: This hook runs yamllint.
entry: yamllint
language: python
files: \.(yaml|yml)$

5
setup.cfg Normal file
View File

@@ -0,0 +1,5 @@
[bdist_wheel]
universal = 1
[flake8]
import-order-style = pep8

View File

@@ -30,7 +30,7 @@ setup(
keywords=['yaml', 'lint', 'linter', 'syntax', 'checker'], keywords=['yaml', 'lint', 'linter', 'syntax', 'checker'],
url='https://github.com/adrienverge/yamllint', url='https://github.com/adrienverge/yamllint',
classifiers=[ classifiers=[
'Development Status :: 4 - Beta', '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)',
@@ -42,10 +42,10 @@ setup(
'Topic :: Software Development :: Testing', 'Topic :: Software Development :: Testing',
], ],
packages=find_packages(), 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/*.yml']}, package_data={'yamllint': ['conf/*.yaml'],
install_requires=['pyyaml'], 'tests': ['yaml-1.2-spec-examples/*']},
tests_require=['nose'], install_requires=['pathspec >=0.5.3', 'pyyaml'],
test_suite='nose.collector', test_suite='tests',
) )

View File

@@ -0,0 +1,20 @@
# -*- 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/>.
import locale
locale.setlocale(locale.LC_ALL, 'C')

View File

@@ -14,7 +14,14 @@
# 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 unittest import os
import tempfile
import sys
try:
assert sys.version_info >= (2, 7)
import unittest
except:
import unittest2 as unittest
import yaml import yaml
@@ -49,3 +56,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

@@ -35,11 +35,15 @@ class CommentsTestCase(RuleTestCase):
' #comment 3 bis\n' ' #comment 3 bis\n'
' # comment 3 ter\n' ' # comment 3 ter\n'
'\n' '\n'
'################################\n'
'## comment 4\n'
'##comment 5\n'
'\n'
'string: "Une longue phrase." # this is French\n', conf) 'string: "Une longue phrase." # this is French\n', conf)
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'
@@ -52,7 +56,11 @@ class CommentsTestCase(RuleTestCase):
'# comment 2\n' '# comment 2\n'
'# comment 3\n' '# comment 3\n'
' # comment 3 bis\n' ' # comment 3 bis\n'
' # comment 3 ter\n', conf) ' # comment 3 ter\n'
'\n'
'################################\n'
'## comment 4\n'
'## comment 5\n', conf)
self.check('---\n' self.check('---\n'
'#comment\n' '#comment\n'
'\n' '\n'
@@ -63,13 +71,18 @@ class CommentsTestCase(RuleTestCase):
'# comment 2\n' '# comment 2\n'
'#comment 3\n' '#comment 3\n'
' #comment 3 bis\n' ' #comment 3 bis\n'
' # comment 3 ter\n', conf, ' # comment 3 ter\n'
'\n'
'################################\n'
'## comment 4\n'
'##comment 5\n', conf,
problem1=(2, 2), problem2=(6, 13), problem1=(2, 2), problem2=(6, 13),
problem4=(9, 2), problem5=(10, 4)) problem3=(9, 2), problem4=(10, 4),
problem5=(15, 3))
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'
@@ -91,7 +104,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'
@@ -106,17 +119,22 @@ class CommentsTestCase(RuleTestCase):
' #comment 3 bis\n' ' #comment 3 bis\n'
' # comment 3 ter\n' ' # comment 3 ter\n'
'\n' '\n'
'################################\n'
'## comment 4\n'
'##comment 5\n'
'\n'
'string: "Une longue phrase." # this is French\n', conf, 'string: "Une longue phrase." # this is French\n', conf,
problem1=(2, 2), problem1=(2, 2),
problem2=(4, 7), problem2=(4, 7),
problem3=(6, 11), problem4=(6, 12), problem3=(6, 11), problem4=(6, 12),
problem5=(9, 2), problem5=(9, 2),
problem6=(10, 4), problem6=(10, 4),
problem7=(13, 30)) problem7=(15, 3),
problem8=(17, 30))
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'
@@ -128,13 +146,21 @@ 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):
conf = ('comments:\n'
' require-starting-space: true\n'
' min-spaces-from-content: 2\n'
'new-line-at-end-of-file: disable\n')
self.check('# comment with no newline char:\n'
'#', conf)
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

@@ -49,7 +49,7 @@ class CommentsIndentationTestCase(RuleTestCase):
'...\n', conf) '...\n', conf)
def test_enabled(self): def test_enabled(self):
conf = 'comments-indentation: {}' conf = 'comments-indentation: enable'
self.check('---\n' self.check('---\n'
'# line 1\n' '# line 1\n'
'# line 2\n', conf) '# line 2\n', conf)
@@ -58,7 +58,7 @@ class CommentsIndentationTestCase(RuleTestCase):
'# line 2\n', conf, problem=(2, 2)) '# line 2\n', conf, problem=(2, 2))
self.check('---\n' self.check('---\n'
' # line 1\n' ' # line 1\n'
' # line 2\n', conf, problem1=(2, 3), problem2=(3, 3)) ' # line 2\n', conf, problem1=(2, 3))
self.check('---\n' self.check('---\n'
'obj:\n' 'obj:\n'
' # normal\n' ' # normal\n'
@@ -102,13 +102,13 @@ class CommentsIndentationTestCase(RuleTestCase):
' a: 1\n' ' a: 1\n'
' # b: 2\n' ' # b: 2\n'
'# this object is useless\n' '# this object is useless\n'
'obj2: no\n', conf) 'obj2: "no"\n', conf)
self.check('---\n' self.check('---\n'
'obj1:\n' 'obj1:\n'
' a: 1\n' ' a: 1\n'
'# this object is useless\n' '# this object is useless\n'
' # b: 2\n' ' # b: 2\n'
'obj2: no\n', conf, problem=(5, 3)) 'obj2: "no"\n', conf, problem=(5, 3))
self.check('---\n' self.check('---\n'
'obj1:\n' 'obj1:\n'
' a: 1\n' ' a: 1\n'
@@ -123,18 +123,18 @@ class CommentsIndentationTestCase(RuleTestCase):
'...\n', conf) '...\n', conf)
def test_first_line(self): def test_first_line(self):
conf = 'comments-indentation: {}' conf = 'comments-indentation: enable'
self.check('# comment\n', conf) self.check('# comment\n', conf)
self.check(' # comment\n', conf, problem=(1, 3)) self.check(' # comment\n', conf, problem=(1, 3))
def test_no_newline_at_end(self): def test_no_newline_at_end(self):
conf = ('comments-indentation: {}\n' conf = ('comments-indentation: enable\n'
'new-line-at-end-of-file: disable\n') 'new-line-at-end-of-file: disable\n')
self.check('# comment', conf) self.check('# comment', conf)
self.check(' # comment', conf, problem=(1, 3)) self.check(' # comment', conf, problem=(1, 3))
def test_empty_comment(self): def test_empty_comment(self):
conf = 'comments-indentation: {}' conf = 'comments-indentation: enable'
self.check('---\n' self.check('---\n'
'# hey\n' '# hey\n'
'# normal\n' '# normal\n'
@@ -143,3 +143,15 @@ class CommentsIndentationTestCase(RuleTestCase):
'# hey\n' '# hey\n'
'# normal\n' '# normal\n'
' #\n', conf, problem=(4, 2)) ' #\n', conf, problem=(4, 2))
def test_inline_comment(self):
conf = 'comments-indentation: enable'
self.check('---\n'
'- a # inline\n'
'# ok\n', conf)
self.check('---\n'
'- a # inline\n'
' # not ok\n', conf, problem=(3, 2))
self.check('---\n'
' # not ok\n'
'- a # inline\n', conf, problem=(2, 2))

View File

@@ -18,8 +18,7 @@ import unittest
import yaml import yaml
from yamllint.rules.common import (Comment, get_line_indent, from yamllint.rules.common import get_line_indent
get_comments_between_tokens)
class CommonTestCase(unittest.TestCase): class CommonTestCase(unittest.TestCase):
@@ -43,54 +42,3 @@ class CommonTestCase(unittest.TestCase):
self.assertEqual(get_line_indent(tokens[i]), 0) self.assertEqual(get_line_indent(tokens[i]), 0)
for i in (13, 16, 18, 22, 24): for i in (13, 16, 18, 22, 24):
self.assertEqual(get_line_indent(tokens[i]), 2) self.assertEqual(get_line_indent(tokens[i]), 2)
def check_comments(self, buffer, *expected):
yaml_loader = yaml.BaseLoader(buffer)
comments = []
next = yaml_loader.peek_token()
while next is not None:
curr = yaml_loader.get_token()
next = yaml_loader.peek_token()
for comment in get_comments_between_tokens(curr, next):
comments.append(comment)
self.assertEqual(comments, list(expected))
def test_get_comments_between_tokens(self):
self.check_comments('# comment\n',
Comment(1, 1, '# comment', 0))
self.check_comments('---\n'
'# comment\n'
'...\n',
Comment(2, 1, '# comment', 0))
self.check_comments('---\n'
'# no newline char',
Comment(2, 1, '# no newline char', 0))
self.check_comments('# just comment',
Comment(1, 1, '# just comment', 0))
self.check_comments('\n'
' # indented comment\n',
Comment(2, 4, '# indented comment', 0))
self.check_comments('\n'
'# trailing spaces \n',
Comment(2, 1, '# trailing spaces ', 0))
self.check_comments('# comment one\n'
'\n'
'key: val # key=val\n'
'\n'
'# this is\n'
'# a block \n'
'# comment\n'
'\n'
'other:\n'
' - foo # equals\n'
' # bar\n',
Comment(1, 1, '# comment one', 0),
Comment(3, 11, '# key=val', 0),
Comment(5, 1, '# this is', 0),
Comment(6, 1, '# a block ', 0),
Comment(7, 1, '# comment', 0),
Comment(10, 10, '# equals', 0),
Comment(11, 10, '# bar', 0))

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'

File diff suppressed because it is too large Load Diff

View File

@@ -80,7 +80,7 @@ class KeyDuplicatesTestCase(RuleTestCase):
': 1\n', conf) ': 1\n', conf)
def test_enabled(self): def test_enabled(self):
conf = 'key-duplicates: {}' conf = 'key-duplicates: enable'
self.check('---\n' self.check('---\n'
'block mapping:\n' 'block mapping:\n'
' key: a\n' ' key: a\n'
@@ -149,7 +149,7 @@ class KeyDuplicatesTestCase(RuleTestCase):
problem4=(7, 3)) problem4=(7, 3))
def test_key_tokens_in_flow_sequences(self): def test_key_tokens_in_flow_sequences(self):
conf = 'key-duplicates: {}' conf = 'key-duplicates: enable'
self.check('---\n' self.check('---\n'
'[\n' '[\n'
' flow: sequence, with, key: value, mappings\n' ' flow: sequence, with, key: value, mappings\n'

View File

@@ -32,6 +32,9 @@ class LineLengthTestCase(RuleTestCase):
self.check('---\n' + 81 * 'a' + '\n', conf) self.check('---\n' + 81 * 'a' + '\n', conf)
self.check(1000 * 'b', conf) self.check(1000 * 'b', conf)
self.check('---\n' + 1000 * 'b' + '\n', conf) self.check('---\n' + 1000 * 'b' + '\n', conf)
self.check('content: |\n'
' {% this line is' + 99 * ' really' + ' long %}\n',
conf)
def test_default(self): def test_default(self):
conf = ('line-length: {max: 80}\n' conf = ('line-length: {max: 80}\n'
@@ -63,7 +66,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'
@@ -78,8 +81,17 @@ class LineLengthTestCase(RuleTestCase):
' # http://localhost/very/long/url\n' ' # http://localhost/very/long/url\n'
' comment\n' ' comment\n'
'...\n', conf) '...\n', conf)
self.check('---\n'
'this:\n'
'is:\n'
'another:\n'
' - https://localhost/very/very/long/url\n'
'...\n', conf)
self.check('---\n'
'long_line: http://localhost/very/very/long/url\n', conf,
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'
@@ -94,3 +106,52 @@ class LineLengthTestCase(RuleTestCase):
' # http://localhost/very/long/url\n' ' # http://localhost/very/long/url\n'
' comment\n' ' comment\n'
'...\n', conf, problem=(5, 21)) '...\n', conf, problem=(5, 21))
self.check('---\n'
'this:\n'
'is:\n'
'another:\n'
' - https://localhost/very/very/long/url\n'
'...\n', conf, problem=(5, 21))
self.check('---\n'
'long_line: http://localhost/very/very/long/url\n'
'...\n', conf, problem=(2, 21))
conf = ('line-length: {max: 20, allow-non-breakable-words: true}\n'
'trailing-spaces: disable')
self.check('---\n'
'loooooooooong+word+and+some+space+at+the+end \n',
conf, problem=(2, 21))
def test_non_breakable_inline_mappings(self):
conf = 'line-length: {max: 20, ' \
'allow-non-breakable-inline-mappings: true}'
self.check('---\n'
'long_line: http://localhost/very/very/long/url\n'
'long line: http://localhost/very/very/long/url\n', conf)
self.check('---\n'
'- long line: http://localhost/very/very/long/url\n', conf)
self.check('---\n'
'long_line: http://localhost/short/url + word\n'
'long line: http://localhost/short/url + word\n',
conf, problem1=(2, 21), problem2=(3, 21))
conf = ('line-length: {max: 20,'
' allow-non-breakable-inline-mappings: true}\n'
'trailing-spaces: disable')
self.check('---\n'
'long_line: and+some+space+at+the+end \n',
conf, problem=(2, 21))
self.check('---\n'
'long line: and+some+space+at+the+end \n',
conf, problem=(2, 21))
self.check('---\n'
'- long line: and+some+space+at+the+end \n',
conf, problem=(2, 21))
# See https://github.com/adrienverge/yamllint/issues/21
conf = 'line-length: {allow-non-breakable-inline-mappings: true}'
self.check('---\n'
'content: |\n'
' {% this line is' + 99 * ' really' + ' long %}\n',
conf, problem=(3, 81))

View File

@@ -30,7 +30,7 @@ class NewLineAtEndOfFileTestCase(RuleTestCase):
self.check('Sentence.\n', conf) self.check('Sentence.\n', conf)
def test_enabled(self): def test_enabled(self):
conf = ('new-line-at-end-of-file: {}\n' conf = ('new-line-at-end-of-file: enable\n'
'empty-lines: disable\n' 'empty-lines: disable\n'
'document-start: disable\n') 'document-start: disable\n')
self.check('', conf) self.check('', conf)

View File

@@ -29,7 +29,7 @@ class TrailingSpacesTestCase(RuleTestCase):
'some: text \n', conf) 'some: text \n', conf)
def test_enabled(self): def test_enabled(self):
conf = 'trailing-spaces: {}' conf = 'trailing-spaces: enable'
self.check('', conf) self.check('', conf)
self.check('\n', conf) self.check('\n', conf)
self.check(' \n', conf, problem=(1, 1)) self.check(' \n', conf, problem=(1, 1))
@@ -40,7 +40,7 @@ class TrailingSpacesTestCase(RuleTestCase):
'some: text\t\n', conf, problem=(2, 11, 'syntax')) 'some: text\t\n', conf, problem=(2, 11, 'syntax'))
def test_with_dos_new_lines(self): def test_with_dos_new_lines(self):
conf = ('trailing-spaces: {}\n' conf = ('trailing-spaces: enable\n'
'new-lines: {type: dos}\n') 'new-lines: {type: dos}\n')
self.check('---\r\n' self.check('---\r\n'
'some: text\r\n', conf) 'some: text\r\n', conf)

View File

@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Peter Ericson
#
# 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 TruthyTestCase(RuleTestCase):
rule_id = 'truthy'
def test_disabled(self):
conf = 'truthy: disable'
self.check('---\n'
'1: True\n', conf)
self.check('---\n'
'True: 1\n', conf)
def test_enabled(self):
conf = 'truthy: enable\n'
self.check('---\n'
'1: True\n'
'True: 1\n',
conf, problem1=(2, 4), problem2=(3, 1))
self.check('---\n'
'1: "True"\n'
'"True": 1\n', conf)
self.check('---\n'
'[\n'
' true, false,\n'
' "false", "FALSE",\n'
' "true", "True",\n'
' True, FALSE,\n'
' on, OFF,\n'
' NO, Yes\n'
']\n', conf,
problem1=(6, 3), problem2=(6, 9),
problem3=(7, 3), problem4=(7, 7),
problem5=(8, 3), problem6=(8, 7))
def test_explicit_types(self):
conf = 'truthy: enable\n'
self.check('---\n'
'string1: !!str True\n'
'string2: !!str yes\n'
'string3: !!str off\n'
'encoded: !!binary |\n'
' True\n'
' OFF\n'
' pad==\n' # this decodes as 'N\xbb\x9e8Qii'
'boolean1: !!bool true\n'
'boolean2: !!bool "false"\n'
'boolean3: !!bool FALSE\n'
'boolean4: !!bool True\n'
'boolean5: !!bool off\n'
'boolean6: !!bool NO\n',
conf)

382
tests/test_cli.py Normal file
View File

@@ -0,0 +1,382 @@
# -*- 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/>.
try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
import fcntl
import locale
import os
import pty
import shutil
import sys
try:
assert sys.version_info >= (2, 7)
import unittest
except:
import unittest2 as unittest
from yamllint import cli
from tests.common import build_temp_workspace
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
class CommandLineTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
super(CommandLineTestCase, cls).setUpClass()
cls.wd = build_temp_workspace({
# .yaml file at root
'a.yaml': '---\n'
'- 1 \n'
'- 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'),
})
@classmethod
def tearDownClass(cls):
super(CommandLineTestCase, cls).tearDownClass()
shutil.rmtree(cls.wd)
def test_find_files_recursively(self):
self.assertEqual(
sorted(cli.find_files_recursively([self.wd])),
[os.path.join(self.wd, 'a.yaml'),
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, 'sub/ok.yaml'),
os.path.join(self.wd, 'warn.yaml')],
)
items = [os.path.join(self.wd, 'sub/ok.yaml'),
os.path.join(self.wd, 'empty-dir')]
self.assertEqual(
sorted(cli.find_files_recursively(items)),
[os.path.join(self.wd, 'sub/ok.yaml')],
)
items = [os.path.join(self.wd, 'empty.yml'),
os.path.join(self.wd, 's')]
self.assertEqual(
sorted(cli.find_files_recursively(items)),
[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')],
)
items = [os.path.join(self.wd, 'sub'),
os.path.join(self.wd, '/etc/another/file')]
self.assertEqual(
sorted(cli.find_files_recursively(items)),
[os.path.join(self.wd, '/etc/another/file'),
os.path.join(self.wd, 'sub/ok.yaml')],
)
def test_run_with_bad_arguments(self):
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(())
self.assertNotEqual(ctx.exception.code, 0)
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', ))
self.assertNotEqual(ctx.exception.code, 0)
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'))
self.assertNotEqual(ctx.exception.code, 0)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, '')
self.assertRegexpMatches(
err.splitlines()[-1],
r'^yamllint: error: argument -d\/--config-data: '
r'not allowed with argument -c\/--config-file$'
)
def test_run_with_bad_config(self):
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-d', 'rules: {a: b}', 'file'))
self.assertEqual(ctx.exception.code, -1)
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):
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-d', '', 'file'))
self.assertEqual(ctx.exception.code, -1)
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):
with open(os.path.join(self.wd, 'config'), 'w') as f:
f.write('rules: {trailing-spaces: disable}')
with self.assertRaises(SystemExit) as ctx:
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
self.assertEqual(ctx.exception.code, 0)
with open(os.path.join(self.wd, 'config'), 'w') as f:
f.write('rules: {trailing-spaces: enable}')
with self.assertRaises(SystemExit) as ctx:
cli.run(('-c', f.name, os.path.join(self.wd, 'a.yaml')))
self.assertEqual(ctx.exception.code, 1)
def test_run_with_user_global_config_file(self):
home = os.path.join(self.wd, 'fake-home')
os.mkdir(home)
dir = os.path.join(home, '.config')
os.mkdir(dir)
dir = os.path.join(dir, 'yamllint')
os.mkdir(dir)
config = os.path.join(dir, 'config')
temp = os.environ['HOME']
os.environ['HOME'] = home
with open(config, 'w') as f:
f.write('rules: {trailing-spaces: disable}')
with self.assertRaises(SystemExit) as ctx:
cli.run((os.path.join(self.wd, 'a.yaml'), ))
self.assertEqual(ctx.exception.code, 0)
with open(config, 'w') as f:
f.write('rules: {trailing-spaces: enable}')
with self.assertRaises(SystemExit) as ctx:
cli.run((os.path.join(self.wd, 'a.yaml'), ))
self.assertEqual(ctx.exception.code, 1)
os.environ['HOME'] = temp
def test_run_version(self):
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('--version', ))
self.assertEqual(ctx.exception.code, 0)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertRegexpMatches(out + err, r'yamllint \d+\.\d+')
def test_run_non_existing_file(self):
file = os.path.join(self.wd, 'i-do-not-exist.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', file))
self.assertEqual(ctx.exception.code, -1)
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):
file = os.path.join(self.wd, 'a.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', file))
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:3:4: [error] no new line character at the end of file '
'(new-line-at-end-of-file)\n') % (file, file))
self.assertEqual(err, '')
def test_run_one_warning(self):
file = os.path.join(self.wd, 'warn.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', file))
self.assertEqual(ctx.exception.code, 0)
def test_run_warning_in_strict_mode(self):
file = os.path.join(self.wd, 'warn.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', '--strict', file))
self.assertEqual(ctx.exception.code, 2)
def test_run_one_ok_file(self):
file = os.path.join(self.wd, 'sub', 'ok.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', file))
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):
file = os.path.join(self.wd, 'empty.yml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', file))
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):
file = os.path.join(self.wd, 'non-ascii', 'utf-8')
# Make sure the default localization conditions on this "system"
# support UTF-8 encoding.
loc = locale.getlocale()
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(('-f', 'parsable', file))
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):
items = [os.path.join(self.wd, 'empty.yml'),
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'
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run(['-f', 'parsable'] + items)
self.assertEqual(ctx.exception.code, 1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(out, (
'%s:3:1: [error] duplication of key "key" in mapping '
'(key-duplicates)\n') % file)
self.assertEqual(err, '')
def test_run_piped_output_nocolor(self):
file = os.path.join(self.wd, 'a.yaml')
sys.stdout, sys.stderr = StringIO(), StringIO()
with self.assertRaises(SystemExit) as ctx:
cli.run((file, ))
self.assertEqual(ctx.exception.code, 1)
out, err = sys.stdout.getvalue(), sys.stderr.getvalue()
self.assertEqual(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' % file))
self.assertEqual(err, '')
def test_run_colored_output(self):
file = os.path.join(self.wd, 'a.yaml')
# Create a pseudo-TTY and redirect stdout to it
master, slave = pty.openpty()
sys.stdout = sys.stderr = os.fdopen(slave, 'w')
with self.assertRaises(SystemExit) as ctx:
cli.run((file, ))
sys.stdout.flush()
self.assertEqual(ctx.exception.code, 1)
# Read output from TTY
output = os.fdopen(master, 'r')
flag = fcntl.fcntl(master, fcntl.F_GETFD)
fcntl.fcntl(master, fcntl.F_SETFL, flag | os.O_NONBLOCK)
out = output.read().replace('\r\n', '\n')
sys.stdout.close()
sys.stderr.close()
output.close()
self.assertEqual(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' % file))

View File

@@ -14,10 +14,24 @@
# 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 unittest try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
import os
import shutil
import sys
try:
assert sys.version_info >= (2, 7)
import unittest
except:
import unittest2 as unittest
from yamllint import cli
from yamllint import config from yamllint import config
from tests.common import build_temp_workspace
class SimpleConfigTestCase(unittest.TestCase): class SimpleConfigTestCase(unittest.TestCase):
def test_parse_config(self): def test_parse_config(self):
@@ -30,14 +44,18 @@ 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):
with self.assertRaises(config.YamlLintConfigError):
config.YamlLintConfig('not: valid: yaml')
def test_unknown_rule(self): def test_unknown_rule(self):
with self.assertRaisesRegexp( with self.assertRaisesRegexp(
config.YamlLintConfigError, config.YamlLintConfigError,
'invalid config: no such rule: "this-one-does-not-exist"'): 'invalid config: no such rule: "this-one-does-not-exist"'):
config.YamlLintConfig('rules:\n' config.YamlLintConfig('rules:\n'
' this-one-does-not-exist: {}\n') ' this-one-does-not-exist: enable\n')
def test_missing_option(self): def test_missing_option(self):
with self.assertRaisesRegexp( with self.assertRaisesRegexp(
@@ -58,6 +76,97 @@ 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.assertEqual(c.rules['indentation']['indent-sequences'], True)
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.assertEqual(c.rules['indentation']['indent-sequences'], True)
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_validate_rule_conf(self):
class Rule(object):
ID = 'fake'
self.assertEqual(config.validate_rule_conf(Rule, False), False)
self.assertEqual(config.validate_rule_conf(Rule, 'disable'), False)
self.assertEqual(config.validate_rule_conf(Rule, {}),
{'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': 'warning'})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'level': 'warn'})
Rule.CONF = {'length': int}
config.validate_rule_conf(Rule, {'length': 8})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'height': 8})
Rule.CONF = {'a': bool, 'b': int}
config.validate_rule_conf(Rule, {'a': True, 'b': 0})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'a': True})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'b': 0})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'a': 1, 'b': 0})
Rule.CONF = {'choice': (True, 88, 'str')}
config.validate_rule_conf(Rule, {'choice': True})
config.validate_rule_conf(Rule, {'choice': 88})
config.validate_rule_conf(Rule, {'choice': 'str'})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': False})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': 99})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': 'abc'})
Rule.CONF = {'choice': (int, 'hardcoded')}
config.validate_rule_conf(Rule, {'choice': 42})
config.validate_rule_conf(Rule, {'choice': 'hardcoded'})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': False})
self.assertRaises(config.YamlLintConfigError,
config.validate_rule_conf, Rule, {'choice': 'abc'})
class ExtendedConfigTestCase(unittest.TestCase): class ExtendedConfigTestCase(unittest.TestCase):
def test_extend_add_rule(self): def test_extend_add_rule(self):
@@ -75,7 +184,7 @@ 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_remove_rule(self): def test_extend_remove_rule(self):
old = config.YamlLintConfig('rules:\n' old = config.YamlLintConfig('rules:\n'
@@ -92,7 +201,7 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['colons'], False) self.assertEqual(new.rules['colons'], False)
self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2) self.assertEqual(new.rules['hyphens']['max-spaces-after'], 2)
self.assertEqual(len(new.enabled_rules()), 1) self.assertEqual(len(new.enabled_rules(None)), 1)
def test_extend_edit_rule(self): def test_extend_edit_rule(self):
old = config.YamlLintConfig('rules:\n' old = config.YamlLintConfig('rules:\n'
@@ -112,7 +221,7 @@ class ExtendedConfigTestCase(unittest.TestCase):
self.assertEqual(new.rules['colons']['max-spaces-after'], 4) self.assertEqual(new.rules['colons']['max-spaces-after'], 4)
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_reenable_rule(self): def test_extend_reenable_rule(self):
old = config.YamlLintConfig('rules:\n' old = config.YamlLintConfig('rules:\n'
@@ -130,7 +239,7 @@ 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)
class ExtendedLibraryConfigTestCase(unittest.TestCase): class ExtendedLibraryConfigTestCase(unittest.TestCase):
@@ -175,3 +284,94 @@ 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])
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)
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
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()))
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((
'./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,
)))

62
tests/test_linter.py Normal file
View File

@@ -0,0 +1,62 @@
# -*- 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/>.
import io
import sys
try:
assert sys.version_info >= (2, 7)
import unittest
except:
import unittest2 as unittest
from yamllint.config import YamlLintConfig
from yamllint import linter
class LinterTestCase(unittest.TestCase):
def fake_config(self):
return YamlLintConfig('extends: default')
def test_run_on_string(self):
linter.run('test: document', self.fake_config())
def test_run_on_bytes(self):
linter.run(b'test: document', self.fake_config())
def test_run_on_unicode(self):
linter.run(u'test: document', self.fake_config())
def test_run_on_stream(self):
linter.run(io.StringIO(u'hello'), self.fake_config())
def test_run_on_int(self):
self.assertRaises(TypeError, linter.run, 42, self.fake_config())
def test_run_on_list(self):
self.assertRaises(TypeError, linter.run,
['h', 'e', 'l', 'l', 'o'], self.fake_config())
def test_run_on_non_ascii_chars(self):
s = (u'- hétérogénéité\n'
u'# 19.99 €\n')
linter.run(s, self.fake_config())
linter.run(s.encode('utf-8'), self.fake_config())
linter.run(s.encode('iso-8859-15'), self.fake_config())
s = (u'- お早う御座います。\n'
u'# الأَبْجَدِيَّة العَرَبِيَّة\n')
linter.run(s, self.fake_config())
linter.run(s.encode('utf-8'), self.fake_config())

88
tests/test_module.py Normal file
View File

@@ -0,0 +1,88 @@
# -*- 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
try:
assert sys.version_info >= (2, 7)
import unittest
except:
import unittest2 as unittest
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
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

@@ -14,12 +14,18 @@
# 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 unittest import sys
try:
assert sys.version_info >= (2, 7)
import unittest
except:
import unittest2 as unittest
import yaml import yaml
from yamllint.parser import (line_generator, token_generator, from yamllint.parser import (line_generator, token_or_comment_generator,
token_or_line_generator, Line, Token) token_or_comment_or_line_generator,
Line, Token, Comment)
class ParserTestCase(unittest.TestCase): class ParserTestCase(unittest.TestCase):
@@ -61,8 +67,8 @@ class ParserTestCase(unittest.TestCase):
self.assertEqual(e[2].line_no, 3) self.assertEqual(e[2].line_no, 3)
self.assertEqual(e[2].content, 'at the end') self.assertEqual(e[2].content, 'at the end')
def test_token_generator(self): def test_token_or_comment_generator(self):
e = list(token_generator('')) e = list(token_or_comment_generator(''))
self.assertEqual(len(e), 2) self.assertEqual(len(e), 2)
self.assertEqual(e[0].prev, None) self.assertEqual(e[0].prev, None)
self.assertIsInstance(e[0].curr, yaml.Token) self.assertIsInstance(e[0].curr, yaml.Token)
@@ -71,16 +77,74 @@ class ParserTestCase(unittest.TestCase):
self.assertEqual(e[1].curr, e[0].next) self.assertEqual(e[1].curr, e[0].next)
self.assertEqual(e[1].next, None) self.assertEqual(e[1].next, None)
e = list(token_generator('---\n' e = list(token_or_comment_generator('---\n'
'k: v\n')) 'k: v\n'))
self.assertEqual(len(e), 9) self.assertEqual(len(e), 9)
self.assertIsInstance(e[3].curr, yaml.KeyToken) self.assertIsInstance(e[3].curr, yaml.KeyToken)
self.assertIsInstance(e[5].curr, yaml.ValueToken) self.assertIsInstance(e[5].curr, yaml.ValueToken)
def test_token_or_line_generator(self): e = list(token_or_comment_generator('# start comment\n'
e = list(token_or_line_generator('---\n' '- a\n'
'k: v\n')) '- key: val # key=val\n'
self.assertEqual(len(e), 12) '# this is\n'
'# a block \n'
'# comment\n'
'- c\n'
'# end comment\n'))
self.assertEqual(len(e), 21)
self.assertIsInstance(e[1], Comment)
self.assertEqual(e[1], Comment(1, 1, '# start comment', 0))
self.assertEqual(e[11], Comment(3, 13, '# key=val', 0))
self.assertEqual(e[12], Comment(4, 1, '# this is', 0))
self.assertEqual(e[13], Comment(5, 1, '# a block ', 0))
self.assertEqual(e[14], Comment(6, 1, '# comment', 0))
self.assertEqual(e[18], Comment(8, 1, '# end comment', 0))
e = list(token_or_comment_generator('---\n'
'# no newline char'))
self.assertEqual(e[2], Comment(2, 1, '# no newline char', 0))
e = list(token_or_comment_generator('# just comment'))
self.assertEqual(e[1], Comment(1, 1, '# just comment', 0))
e = list(token_or_comment_generator('\n'
' # indented comment\n'))
self.assertEqual(e[1], Comment(2, 4, '# indented comment', 0))
e = list(token_or_comment_generator('\n'
'# trailing spaces \n'))
self.assertEqual(e[1], Comment(2, 1, '# trailing spaces ', 0))
e = [c for c in
token_or_comment_generator('# block\n'
'# comment\n'
'- data # inline comment\n'
'# block\n'
'# comment\n'
'- k: v # inline comment\n'
'- [ l, ist\n'
'] # inline comment\n'
'- { m: ap\n'
'} # inline comment\n'
'# block comment\n'
'- data # inline comment\n')
if isinstance(c, Comment)]
self.assertEqual(len(e), 10)
self.assertFalse(e[0].is_inline())
self.assertFalse(e[1].is_inline())
self.assertTrue(e[2].is_inline())
self.assertFalse(e[3].is_inline())
self.assertFalse(e[4].is_inline())
self.assertTrue(e[5].is_inline())
self.assertTrue(e[6].is_inline())
self.assertTrue(e[7].is_inline())
self.assertFalse(e[8].is_inline())
self.assertTrue(e[9].is_inline())
def test_token_or_comment_or_line_generator(self):
e = list(token_or_comment_or_line_generator('---\n'
'k: v # k=v\n'))
self.assertEqual(len(e), 13)
self.assertIsInstance(e[0], Token) self.assertIsInstance(e[0], Token)
self.assertIsInstance(e[0].curr, yaml.StreamStartToken) self.assertIsInstance(e[0].curr, yaml.StreamStartToken)
self.assertIsInstance(e[1], Token) self.assertIsInstance(e[1], Token)
@@ -89,5 +153,6 @@ class ParserTestCase(unittest.TestCase):
self.assertIsInstance(e[3].curr, yaml.BlockMappingStartToken) self.assertIsInstance(e[3].curr, yaml.BlockMappingStartToken)
self.assertIsInstance(e[4].curr, yaml.KeyToken) self.assertIsInstance(e[4].curr, yaml.KeyToken)
self.assertIsInstance(e[6].curr, yaml.ValueToken) self.assertIsInstance(e[6].curr, yaml.ValueToken)
self.assertIsInstance(e[8], Line) self.assertIsInstance(e[8], Comment)
self.assertIsInstance(e[11], Line) self.assertIsInstance(e[9], Line)
self.assertIsInstance(e[12], Line)

View File

@@ -14,6 +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 io import open
import os import os
from tests.common import RuleTestCase from tests.common import RuleTestCase
@@ -35,6 +36,7 @@ from tests.common import RuleTestCase
# for br in span.find_all("br"): # for br in span.find_all("br"):
# br.replace_with("\n") # br.replace_with("\n")
# text = text.replace('\u2193', '') # downwards arrow # text = text.replace('\u2193', '') # downwards arrow
# text = text.replace('\u21d3', '') # double downwards arrow
# text = text.replace('\u00b7', ' ') # visible space # text = text.replace('\u00b7', ' ') # visible space
# text = text.replace('\u21d4', '') # byte order mark # text = text.replace('\u21d4', '') # byte order mark
# text = text.replace('\u2192', '\t') # right arrow # text = text.replace('\u2192', '\t') # right arrow
@@ -46,6 +48,7 @@ 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'
@@ -64,7 +67,7 @@ conf_overrides = {
'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'),
@@ -95,6 +98,7 @@ conf_overrides = {
'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.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'),
@@ -111,9 +115,17 @@ conf_overrides = {
'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: false}\n'
'colons: {max-spaces-before: 1}\n'),
'example-8.22': ('indentation: disable\n'),
'example-10.1': ('colons: {max-spaces-before: 2}\n'),
'example-10.2': ('indentation: {indent-sequences: false}\n'),
'example-10.8': ('truthy: disable\n'),
'example-10.9': ('truthy: disable\n'),
} }
files = os.listdir('tests/yaml-1.2-spec-examples') files = os.listdir(os.path.join(os.path.dirname(os.path.realpath(__file__)),
'yaml-1.2-spec-examples'))
assert len(files) == 132 assert len(files) == 132
@@ -122,15 +134,7 @@ def _gen_test(buffer, conf):
self.check(buffer, conf) self.check(buffer, conf)
return test return test
# TODO
# The following tests are blacklisted because they contain rarely-used formats
# that yamllint does not handle yet.
tmp_blacklist = (
'example-7.16',
'example-8.20',
'example-8.22',
'example-10.1',
)
# 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()).
@@ -177,10 +181,10 @@ pyyaml_blacklist = (
) )
for file in files: for file in files:
if file in tmp_blacklist or file in pyyaml_blacklist: if file in pyyaml_blacklist:
continue continue
with open('tests/yaml-1.2-spec-examples/' + file) as f: with open('tests/yaml-1.2-spec-examples/' + file, encoding='utf-8') as f:
conf = conf_general + conf_overrides.get(file, '') conf = conf_general + conf_overrides.get(file, '')
setattr(SpecificationTestCase, 'test_' + file, setattr(SpecificationTestCase, 'test_' + file,
_gen_test(f.read(), conf)) _gen_test(f.read(), conf))

View File

@@ -0,0 +1,304 @@
# -*- 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 YamllintDirectivesTestCase(RuleTestCase):
conf = ('commas: disable\n'
'trailing-spaces: {}\n'
'colons: {max-spaces-before: 1}\n')
def test_disable_directive(self):
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(4, 8, 'colons'),
problem3=(6, 7, 'colons'),
problem4=(6, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'# yamllint disable\n'
'- bad : colon\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem=(3, 18, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'# yamllint disable\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'# yamllint enable\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(8, 7, 'colons'),
problem2=(8, 26, 'trailing-spaces'))
def test_disable_directive_with_rules(self):
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'# yamllint disable rule:trailing-spaces\n'
'- bad : colon\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(5, 8, 'colons'),
problem3=(7, 7, 'colons'))
self.check('---\n'
'- [valid , YAML]\n'
'# yamllint disable rule:trailing-spaces\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'# yamllint enable rule:trailing-spaces\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(5, 8, 'colons'),
problem2=(8, 7, 'colons'),
problem3=(8, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'# yamllint disable rule:trailing-spaces\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'# yamllint enable\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(5, 8, 'colons'),
problem2=(8, 7, 'colons'),
problem3=(8, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'# yamllint disable\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'# yamllint enable rule:trailing-spaces\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem=(8, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'# yamllint disable rule:colons\n'
'- trailing spaces \n'
'# yamllint disable rule:trailing-spaces\n'
'- bad : colon\n'
'- [valid , YAML]\n'
'# yamllint enable rule:colons\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(4, 18, 'trailing-spaces'),
problem2=(9, 7, 'colons'))
def test_disable_line_directive(self):
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'# yamllint disable-line\n'
'- bad : colon\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(7, 7, 'colons'),
problem3=(7, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'- bad : colon # yamllint disable-line\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(6, 7, 'colons'),
problem3=(6, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML] # yamllint disable-line\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(4, 8, 'colons'),
problem3=(6, 7, 'colons'),
problem4=(6, 26, 'trailing-spaces'))
def test_disable_line_directive_with_rules(self):
self.check('---\n'
'- [valid , YAML]\n'
'# yamllint disable-line rule:colons\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(4, 18, 'trailing-spaces'),
problem2=(5, 8, 'colons'),
problem3=(7, 7, 'colons'),
problem4=(7, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces # yamllint disable-line rule:colons \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 55, 'trailing-spaces'),
problem2=(4, 8, 'colons'),
problem3=(6, 7, 'colons'),
problem4=(6, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'# yamllint disable-line rule:colons\n'
'- bad : colon\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(7, 7, 'colons'),
problem3=(7, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'- bad : colon # yamllint disable-line rule:colons\n'
'- [valid , YAML]\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(6, 7, 'colons'),
problem3=(6, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'# yamllint disable-line rule:colons\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(4, 8, 'colons'),
problem3=(7, 26, 'trailing-spaces'))
self.check('---\n'
'- [valid , YAML]\n'
'- trailing spaces \n'
'- bad : colon\n'
'- [valid , YAML]\n'
'# yamllint disable-line rule:colons rule:trailing-spaces\n'
'- bad : colon and spaces \n'
'- [valid , YAML]\n',
self.conf,
problem1=(3, 18, 'trailing-spaces'),
problem2=(4, 8, 'colons'))
def test_directive_on_last_line(self):
conf = 'new-line-at-end-of-file: {}'
self.check('---\n'
'no new line',
conf,
problem=(2, 12, 'new-line-at-end-of-file'))
self.check('---\n'
'# yamllint disable\n'
'no new line',
conf)
self.check('---\n'
'no new line # yamllint disable',
conf)
def test_indented_directive(self):
conf = 'brackets: {min-spaces-inside: 0, max-spaces-inside: 0}'
self.check('---\n'
'- a: 1\n'
' b:\n'
' c: [ x]\n',
conf,
problem=(4, 12, 'brackets'))
self.check('---\n'
'- a: 1\n'
' b:\n'
' # yamllint disable-line rule:brackets\n'
' c: [ x]\n',
conf)
def test_directive_on_itself(self):
conf = ('comments: {min-spaces-from-content: 2}\n'
'comments-indentation: {}\n')
self.check('---\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf,
problem1=(2, 8, 'comments'),
problem2=(4, 2, 'comments-indentation'))
self.check('---\n'
'# yamllint disable\n'
'- a: 1 # comment too close\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf)
self.check('---\n'
'- a: 1 # yamllint disable-line\n'
' b:\n'
' # yamllint disable-line\n'
' # wrong indentation\n'
' c: [x]\n',
conf)
self.check('---\n'
'- a: 1 # yamllint disable-line rule:comments\n'
' b:\n'
' # yamllint disable-line rule:comments-indentation\n'
' # wrong indentation\n'
' c: [x]\n',
conf)
self.check('---\n'
'# yamllint disable\n'
'- a: 1 # comment too close\n'
' # yamllint enable rule:comments-indentation\n'
' b:\n'
' # wrong indentation\n'
' c: [x]\n',
conf,
problem=(6, 2, 'comments-indentation'))

View File

@@ -2,7 +2,7 @@
# Comments: # Comments:
strip: |- strip: |-
# text # text
# Clip # Clip
# comments: # comments:

View File

@@ -14,12 +14,16 @@
# 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/>.
APP_NAME = 'yamllint' """A linter for YAML files.
APP_VERSION = '0.7.0'
APP_DESCRIPTION = """A linter for YAML files.
yamllint does not only check for syntax validity, but for common cosmetic yamllint does not only check for syntax validity, but for weirdnesses like key
conventions such as lines length, trailing spaces, indentation, etc.""" repetition and cosmetic problems such as lines length, trailing spaces,
indentation, etc."""
APP_NAME = 'yamllint'
APP_VERSION = '1.8.1'
APP_DESCRIPTION = __doc__
__author__ = u'Adrien Vergé' __author__ = u'Adrien Vergé'
__copyright__ = u'Copyright 2016, Adrien Vergé' __copyright__ = u'Copyright 2016, 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

@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2016 Adrien Vergé # Copyright (C) 2016 Adrien Vergé
# #
@@ -16,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 __future__ import print_function from __future__ import print_function
import os.path import os.path
import sys import sys
@@ -23,6 +23,7 @@ import argparse
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.config import YamlLintConfig, YamlLintConfigError
from yamllint.linter import PROBLEM_LEVELS
from yamllint import linter from yamllint import linter
@@ -49,6 +50,17 @@ class Format(object):
@staticmethod @staticmethod
def standard(problem, filename): def standard(problem, filename):
line = ' %d:%d' % (problem.line, problem.column)
line += max(12 - len(line), 0) * ' '
line += problem.level
line += max(21 - len(line), 0) * ' '
line += problem.desc
if problem.rule:
line += ' (%s)' % problem.rule
return line
@staticmethod
def standard_color(problem, filename):
line = ' \033[2m%d:%d\033[0m' % (problem.line, problem.column) line = ' \033[2m%d:%d\033[0m' % (problem.line, problem.column)
line += max(20 - len(line), 0) * ' ' line += max(20 - len(line), 0) * ' '
if problem.level == 'warning': if problem.level == 'warning':
@@ -67,11 +79,20 @@ def run(argv=None):
description=APP_DESCRIPTION) description=APP_DESCRIPTION)
parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+', parser.add_argument('files', metavar='FILE_OR_DIR', nargs='+',
help='files to check') help='files to check')
parser.add_argument('-c', '--config', dest='config_file', action='store', config_group = parser.add_mutually_exclusive_group()
help='path to a custom configuration') 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'), default='standard',
help='format for parsing output') help='format for parsing output')
parser.add_argument('-s', '--strict',
action='store_true',
help='return non-zero exit code on warnings '
'as well as errors')
parser.add_argument('-v', '--version', action='version', parser.add_argument('-v', '--version', action='version',
version='%s %s' % (APP_NAME, APP_VERSION)) version='%s %s' % (APP_NAME, APP_VERSION))
@@ -79,40 +100,66 @@ def run(argv=None):
args = parser.parse_args(argv) args = parser.parse_args(argv)
# User-global config is supposed to be in ~/.config/yamllint/config
if 'XDG_CONFIG_HOME' in os.environ:
user_global_config = os.path.join(
os.environ['XDG_CONFIG_HOME'], 'yamllint', 'config')
else:
user_global_config = os.path.expanduser('~/.config/yamllint/config')
try: try:
if args.config_file is not None: if args.config_data is not None:
if args.config_data != '' and ':' not in args.config_data:
args.config_data = 'extends: ' + args.config_data
conf = YamlLintConfig(content=args.config_data)
elif args.config_file is not None:
conf = YamlLintConfig(file=args.config_file) 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(user_global_config):
conf = YamlLintConfig(file=user_global_config)
else: else:
conf = YamlLintConfig('extends: default') conf = YamlLintConfig('extends: default')
except YamlLintConfigError as e: except YamlLintConfigError as e:
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):
filepath = file[2:] if file.startswith('./') else file
try: try:
first = True first = True
with open(file) as f: with open(file) as f:
for problem in linter.run(f, conf): for problem in linter.run(f, conf, filepath):
if args.format == 'parsable': if args.format == 'parsable':
print(Format.parsable(problem, file)) print(Format.parsable(problem, file))
else: elif sys.stdout.isatty():
if first: if first:
print('\033[4m%s\033[0m' % file) print('\033[4m%s\033[0m' % file)
first = False first = False
print(Format.standard_color(problem, file))
else:
if first:
print(file)
first = False
print(Format.standard(problem, file)) print(Format.standard(problem, file))
if return_code == 0 and problem.level == 'error': max_level = max(max_level, PROBLEM_LEVELS[problem.level])
return_code = 1
if not first and args.format != 'parsable': if not first and args.format != 'parsable':
print('') print('')
except EnvironmentError as e: except EnvironmentError as e:
print(e) print(e, file=sys.stderr)
return_code = -1 sys.exit(-1)
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

@@ -4,9 +4,13 @@ rules:
braces: braces:
min-spaces-inside: 0 min-spaces-inside: 0
max-spaces-inside: 0 max-spaces-inside: 0
min-spaces-inside-empty: -1
max-spaces-inside-empty: -1
brackets: brackets:
min-spaces-inside: 0 min-spaces-inside: 0
max-spaces-inside: 0 max-spaces-inside: 0
min-spaces-inside-empty: -1
max-spaces-inside-empty: -1
colons: colons:
max-spaces-before: 0 max-spaces-before: 0
max-spaces-after: 1 max-spaces-after: 1
@@ -16,14 +20,14 @@ rules:
max-spaces-after: 1 max-spaces-after: 1
comments: comments:
level: warning level: warning
require-starting-space: yes require-starting-space: true
min-spaces-from-content: 2 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 present: true
empty-lines: empty-lines:
max: 2 max: 2
max-start: 0 max-start: 0
@@ -31,14 +35,17 @@ rules:
hyphens: hyphens:
max-spaces-after: 1 max-spaces-after: 1
indentation: indentation:
spaces: 2 spaces: consistent
indent-sequences: yes indent-sequences: true
check-multi-line-strings: no check-multi-line-strings: false
key-duplicates: {} key-duplicates: enable
line-length: line-length:
max: 80 max: 80
allow-non-breakable-words: yes allow-non-breakable-words: true
new-line-at-end-of-file: {level: error} allow-non-breakable-inline-mappings: false
new-line-at-end-of-file: enable
new-lines: new-lines:
type: unix type: unix
trailing-spaces: {} trailing-spaces: enable
truthy:
level: warning

View File

@@ -0,0 +1,29 @@
---
extends: default
rules:
braces:
level: warning
max-spaces-inside: 1
brackets:
level: warning
max-spaces-inside: 1
colons:
level: warning
commas:
level: warning
comments: disable
comments-indentation: disable
document-start: disable
empty-lines:
level: warning
hyphens:
level: warning
indentation:
level: warning
indent-sequences: consistent
line-length:
level: warning
allow-non-breakable-inline-mappings: true
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,8 @@ 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
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,9 +39,14 @@ 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 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)
@@ -53,12 +61,18 @@ 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:
raise YamlLintConfigError('invalid config: not a dict')
self.rules = conf.get('rules', {}) self.rules = conf.get('rules', {})
# Does this conf override another conf that we need to load? # Does this conf override another conf that we need to load?
@@ -70,6 +84,13 @@ 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 type(conf['ignore']) != str:
raise YamlLintConfigError(
'invalid config: ignore should be a list of patterns')
self.ignore = pathspec.PathSpec.from_lines(
'gitwildmatch', conf['ignore'].splitlines())
def validate(self): def validate(self):
for id in self.rules: for id in self.rules:
try: try:
@@ -83,8 +104,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 or conf == 'disable':
return False return False
elif conf == 'enable':
conf = {}
if type(conf) == dict: if type(conf) == dict:
if ('ignore' in conf and
type(conf['ignore']) != pathspec.pathspec.PathSpec):
if type(conf['ignore']) != str:
raise YamlLintConfigError(
'invalid config: ignore should be a list of 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'):
@@ -93,14 +124,15 @@ def validate_rule_conf(rule, conf):
options = getattr(rule, 'CONF', {}) options = getattr(rule, 'CONF', {})
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: if type(options[optkey]) == tuple:
if conf[optkey] not in options[optkey]: if (conf[optkey] not in options[optkey] and
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]))
@@ -116,7 +148,8 @@ def validate_rule_conf(rule, conf):
(optkey, rule.ID)) (optkey, rule.ID))
else: else:
raise YamlLintConfigError(('invalid config: rule "%s": should be ' raise YamlLintConfigError(('invalid config: rule "%s": should be '
'either "disable" or a dict') % rule.ID) 'either "enable", "disable" or a dict')
% rule.ID)
return conf return conf
@@ -125,7 +158,7 @@ def get_extended_config_file(name):
# Is it a standard conf shipped with yamllint... # Is it a standard conf shipped with yamllint...
if '/' not in name: if '/' not in name:
std_conf = os.path.join(os.path.dirname(os.path.realpath(__file__)), std_conf = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'conf', name + '.yml') 'conf', name + '.yaml')
if os.path.isfile(std_conf): if os.path.isfile(std_conf):
return std_conf return std_conf

View File

@@ -14,11 +14,23 @@
# 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 re
import yaml 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):
@@ -51,35 +63,116 @@ 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']
comment_rules = [r for r in rules if r.TYPE == 'comment']
line_rules = [r for r in rules if r.TYPE == 'line'] line_rules = [r for r in rules if r.TYPE == 'line']
context = {} context = {}
for rule in token_rules: for rule in token_rules:
context[rule.ID] = {} context[rule.ID] = {}
for elem in parser.token_or_line_generator(buffer): class DisableDirective():
def __init__(self):
self.rules = set()
self.all_rules = set([r.ID for r in rules])
def process_comment(self, comment):
try:
comment = str(comment)
except UnicodeError:
return # this certainly wasn't a yamllint directive comment
if re.match(r'^# yamllint disable( rule:\S+)*\s*$', comment):
rules = [item[5:] for item in comment[18:].split(' ')][1:]
if len(rules) == 0:
self.rules = self.all_rules.copy()
else:
for id in rules:
if id in self.all_rules:
self.rules.add(id)
elif re.match(r'^# yamllint enable( rule:\S+)*\s*$', comment):
rules = [item[5:] for item in comment[17:].split(' ')][1:]
if len(rules) == 0:
self.rules.clear()
else:
for id in rules:
self.rules.discard(id)
def is_disabled_by_directive(self, problem):
return problem.rule in self.rules
class DisableLineDirective(DisableDirective):
def process_comment(self, comment):
try:
comment = str(comment)
except UnicodeError:
return # this certainly wasn't a yamllint directive comment
if re.match(r'^# yamllint disable-line( rule:\S+)*\s*$', comment):
rules = [item[5:] for item in comment[23:].split(' ')][1:]
if len(rules) == 0:
self.rules = self.all_rules.copy()
else:
for id in rules:
if id in self.all_rules:
self.rules.add(id)
# Use a cache to store problems and flush it only when a end of line is
# found. This allows the use of yamllint directive to disable some rules on
# some lines.
cache = []
disabled = DisableDirective()
disabled_for_line = DisableLineDirective()
disabled_for_next_line = DisableLineDirective()
for elem in parser.token_or_comment_or_line_generator(buffer):
if isinstance(elem, parser.Token): if isinstance(elem, parser.Token):
for rule in token_rules: for rule in token_rules:
rule_conf = conf.rules[rule.ID] rule_conf = conf.rules[rule.ID]
for problem in rule.check(rule_conf, for problem in rule.check(rule_conf,
elem.curr, elem.prev, elem.next, elem.curr, elem.prev, elem.next,
elem.nextnext,
context[rule.ID]): context[rule.ID]):
problem.rule = rule.ID problem.rule = rule.ID
problem.level = rule_conf['level'] problem.level = rule_conf['level']
yield problem cache.append(problem)
elif isinstance(elem, parser.Comment):
for rule in comment_rules:
rule_conf = conf.rules[rule.ID]
for problem in rule.check(rule_conf, elem):
problem.rule = rule.ID
problem.level = rule_conf['level']
cache.append(problem)
disabled.process_comment(elem)
if elem.is_inline():
disabled_for_line.process_comment(elem)
else:
disabled_for_next_line.process_comment(elem)
elif isinstance(elem, parser.Line): elif isinstance(elem, parser.Line):
for rule in line_rules: for rule in line_rules:
rule_conf = conf.rules[rule.ID] rule_conf = conf.rules[rule.ID]
for problem in rule.check(rule_conf, elem): for problem in rule.check(rule_conf, elem):
problem.rule = rule.ID problem.rule = rule.ID
problem.level = rule_conf['level'] problem.level = rule_conf['level']
cache.append(problem)
# This is the last token/comment/line of this line, let's flush the
# problems found (but filter them according to the directives)
for problem in cache:
if not (disabled_for_line.is_disabled_by_directive(problem) or
disabled.is_disabled_by_directive(problem)):
yield problem yield problem
disabled_for_line = disabled_for_next_line
disabled_for_next_line = DisableLineDirective()
cache = []
def get_syntax_error(buffer): def get_syntax_error(buffer):
try: try:
@@ -92,12 +185,15 @@ def get_syntax_error(buffer):
return problem return problem
def _run(buffer, conf): def _run(buffer, conf, filepath):
assert hasattr(buffer, '__getitem__'), \
'_run() argument must be a buffer, not a stream'
# 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):
@@ -119,7 +215,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.
@@ -127,11 +223,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) == str: if conf.is_file_ignored(filepath):
return _run(input, conf) return ()
if type(input) in (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

@@ -30,11 +30,46 @@ class Line(object):
class Token(object): class Token(object):
def __init__(self, line_no, curr, prev, next): def __init__(self, line_no, curr, prev, next, nextnext):
self.line_no = line_no self.line_no = line_no
self.curr = curr self.curr = curr
self.prev = prev self.prev = prev
self.next = next self.next = next
self.nextnext = nextnext
class Comment(object):
def __init__(self, line_no, column_no, buffer, pointer,
token_before=None, token_after=None, comment_before=None):
self.line_no = line_no
self.column_no = column_no
self.buffer = buffer
self.pointer = pointer
self.token_before = token_before
self.token_after = token_after
self.comment_before = comment_before
def __str__(self):
end = self.buffer.find('\n', self.pointer)
if end == -1:
end = self.buffer.find('\0', self.pointer)
if end != -1:
return self.buffer[self.pointer:end]
return self.buffer[self.pointer:]
def __eq__(self, other):
return (isinstance(other, Comment) and
self.line_no == other.line_no and
self.column_no == other.column_no and
str(self) == str(other))
def is_inline(self):
return (
not isinstance(self.token_before, yaml.StreamStartToken) and
self.line_no == self.token_before.end_mark.line + 1 and
# sometimes token end marks are on the next line
self.buffer[self.token_before.end_mark.pointer - 1] != '\n'
)
def line_generator(buffer): def line_generator(buffer):
@@ -50,36 +85,73 @@ def line_generator(buffer):
yield Line(line_no, buffer, start=cur, end=len(buffer)) yield Line(line_no, buffer, start=cur, end=len(buffer))
def token_generator(buffer): def comments_between_tokens(token1, token2):
"""Find all comments between two tokens"""
if token2 is None:
buf = token1.end_mark.buffer[token1.end_mark.pointer:]
elif (token1.end_mark.line == token2.start_mark.line and
not isinstance(token1, yaml.StreamStartToken) and
not isinstance(token2, yaml.StreamEndToken)):
return
else:
buf = token1.end_mark.buffer[token1.end_mark.pointer:
token2.start_mark.pointer]
line_no = token1.end_mark.line + 1
column_no = token1.end_mark.column + 1
pointer = token1.end_mark.pointer
comment_before = None
for line in buf.split('\n'):
pos = line.find('#')
if pos != -1:
comment = Comment(line_no, column_no + pos,
token1.end_mark.buffer, pointer + pos,
token1, token2, comment_before)
yield comment
comment_before = comment
pointer += len(line) + 1
line_no += 1
column_no = 1
def token_or_comment_generator(buffer):
yaml_loader = yaml.BaseLoader(buffer) yaml_loader = yaml.BaseLoader(buffer)
try: try:
prev = None prev = None
next = yaml_loader.peek_token() curr = yaml_loader.get_token()
while next is not None: while curr is not None:
curr = yaml_loader.get_token() next = yaml_loader.get_token()
next = yaml_loader.peek_token() nextnext = yaml_loader.peek_token()
yield Token(curr.start_mark.line + 1, curr, prev, next) yield Token(curr.start_mark.line + 1, curr, prev, next, nextnext)
for comment in comments_between_tokens(curr, next):
yield comment
prev = curr prev = curr
curr = next
except yaml.scanner.ScannerError: except yaml.scanner.ScannerError:
pass pass
def token_or_line_generator(buffer): def token_or_comment_or_line_generator(buffer):
"""Generator that mixes tokens and lines, ordering them by line number""" """Generator that mixes tokens and lines, ordering them by line number"""
token_gen = token_generator(buffer) tok_or_com_gen = token_or_comment_generator(buffer)
line_gen = line_generator(buffer) line_gen = line_generator(buffer)
token = next(token_gen, None) tok_or_com = next(tok_or_com_gen, None)
line = next(line_gen, None) line = next(line_gen, None)
while token is not None or line is not None: while tok_or_com is not None or line is not None:
if token is None or (line is not None and if tok_or_com is None or (line is not None and
token.line_no > line.line_no): tok_or_com.line_no > line.line_no):
yield line yield line
line = next(line_gen, None) line = next(line_gen, None)
else: else:
yield token yield tok_or_com
token = next(token_gen, None) tok_or_com = next(tok_or_com_gen, None)

View File

@@ -31,6 +31,7 @@ from yamllint.rules import (
new_line_at_end_of_file, new_line_at_end_of_file,
new_lines, new_lines,
trailing_spaces, trailing_spaces,
truthy,
) )
_RULES = { _RULES = {
@@ -50,6 +51,7 @@ _RULES = {
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,
trailing_spaces.ID: trailing_spaces, trailing_spaces.ID: trailing_spaces,
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,27 @@ 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}
def check(conf, token, prev, next, 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,28 @@ 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}
def check(conf, token, prev, next, 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

@@ -81,7 +81,7 @@ CONF = {'max-spaces-before': int,
'max-spaces-after': int} 'max-spaces-after': int}
def check(conf, token, prev, next, context): def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.ValueToken): if isinstance(token, yaml.ValueToken):
problem = spaces_before(token, prev, next, problem = spaces_before(token, prev, next,
max=conf['max-spaces-before'], max=conf['max-spaces-before'],

View File

@@ -105,7 +105,7 @@ CONF = {'max-spaces-before': int,
'max-spaces-after': int} 'max-spaces-after': int}
def check(conf, token, prev, next, context): def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.FlowEntryToken): if isinstance(token, yaml.FlowEntryToken):
if (prev is not None and conf['max-spaces-before'] != -1 and if (prev is not None and conf['max-spaces-before'] != -1 and
prev.end_mark.line < token.start_mark.line): prev.end_mark.line < token.start_mark.line):

View File

@@ -20,14 +20,14 @@ 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.
* ``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**:
:: ::
@@ -35,6 +35,12 @@ Use this rule to control the position and formatting of comments.
# This sentence # This sentence
# is a block comment # is a block comment
the following code snippet would **PASS**:
::
##############################
## This is some documentation
the following code snippet would **FAIL**: the following code snippet would **FAIL**:
:: ::
@@ -55,33 +61,29 @@ Use this rule to control the position and formatting of comments.
""" """
import yaml
from yamllint.linter import LintProblem from yamllint.linter import LintProblem
from yamllint.rules.common import get_comments_between_tokens
ID = 'comments' ID = 'comments'
TYPE = 'token' TYPE = 'comment'
CONF = {'require-starting-space': bool, CONF = {'require-starting-space': bool,
'min-spaces-from-content': int} 'min-spaces-from-content': int}
def check(conf, token, prev, next, context): def check(conf, comment):
for comment in get_comments_between_tokens(token, next): if (conf['min-spaces-from-content'] != -1 and comment.is_inline() and
if (conf['min-spaces-from-content'] != -1 and comment.pointer - comment.token_before.end_mark.pointer <
not isinstance(token, yaml.StreamStartToken) and conf['min-spaces-from-content']):
comment.line == token.end_mark.line + 1): yield LintProblem(comment.line_no, comment.column_no,
# Sometimes token end marks are on the next line 'too few spaces before comment')
if token.end_mark.buffer[token.end_mark.pointer - 1] != '\n':
if (comment.pointer - token.end_mark.pointer <
conf['min-spaces-from-content']):
yield LintProblem(comment.line, comment.column,
'too few spaces before comment')
if (conf['require-starting-space'] and if conf['require-starting-space']:
comment.pointer + 1 < len(comment.buffer) and text_start = comment.pointer + 1
comment.buffer[comment.pointer + 1] != ' ' and while (comment.buffer[text_start] == '#' and
comment.buffer[comment.pointer + 1] != '\n'): text_start < len(comment.buffer)):
yield LintProblem(comment.line, comment.column + 1, text_start += 1
if (text_start < len(comment.buffer) and
comment.buffer[text_start] not in (' ', '\n', '\0')):
yield LintProblem(comment.line_no,
comment.column_no + text_start - comment.pointer,
'missing starting space in comment') 'missing starting space in comment')

View File

@@ -78,11 +78,11 @@ Use this rule to force comments to be indented like content.
import yaml import yaml
from yamllint.linter import LintProblem from yamllint.linter import LintProblem
from yamllint.rules.common import get_line_indent, get_comments_between_tokens from yamllint.rules.common import get_line_indent
ID = 'comments-indentation' ID = 'comments-indentation'
TYPE = 'token' TYPE = 'comment'
# Case A: # Case A:
@@ -98,28 +98,42 @@ TYPE = 'token'
# # commented line 2 # # commented line 2
# current: line # current: line
def check(conf, token, prev, next, context): def check(conf, comment):
if prev is None: # Only check block comments
if (not isinstance(comment.token_before, yaml.StreamStartToken) and
comment.token_before.end_mark.line + 1 == comment.line_no):
return return
curr_line_indent = token.start_mark.column next_line_indent = comment.token_after.start_mark.column
if isinstance(token, yaml.StreamEndToken): if isinstance(comment.token_after, yaml.StreamEndToken):
curr_line_indent = 0 next_line_indent = 0
skip_first_line = True if isinstance(comment.token_before, yaml.StreamStartToken):
if isinstance(prev, yaml.StreamStartToken):
skip_first_line = False
prev_line_indent = 0 prev_line_indent = 0
else: else:
prev_line_indent = get_line_indent(prev) prev_line_indent = get_line_indent(comment.token_before)
if prev_line_indent <= curr_line_indent: # In the following case only the next line indent is valid:
prev_line_indent = -1 # disable it # list:
# # comment
# - 1
# - 2
if prev_line_indent <= next_line_indent:
prev_line_indent = next_line_indent
for comment in get_comments_between_tokens( # If two indents are valid but a previous comment went back to normal
prev, token, skip_first_line=skip_first_line): # indent, for the next ones to do the same. In other words, avoid this:
if comment.column - 1 == curr_line_indent: # list:
prev_line_indent = -1 # disable it # - 1
elif comment.column - 1 != prev_line_indent: # # comment on valid indent (0)
yield LintProblem(comment.line, comment.column, # # comment on valid indent (4)
'comment not indented like content') # other-list:
# - 2
if (comment.comment_before is not None and
not comment.comment_before.is_inline()):
prev_line_indent = comment.comment_before.column_no - 1
if (comment.column_no - 1 != prev_line_indent and
comment.column_no - 1 != next_line_indent):
yield LintProblem(comment.line_no, comment.column_no,
'comment not indented like content')

View File

@@ -48,27 +48,6 @@ def spaces_before(token, prev, next, min=-1, max=-1,
token.start_mark.column + 1, min_desc) token.start_mark.column + 1, min_desc)
class Comment(object):
def __init__(self, line, column, buffer, pointer):
self.line = line
self.column = column
self.buffer = buffer
self.pointer = pointer
def __repr__(self):
end = self.buffer.find('\n', self.pointer)
if end == -1:
end = self.buffer.find('\0', self.pointer)
if end != -1:
return self.buffer[self.pointer:end]
return self.buffer[self.pointer:]
def __eq__(self, other):
return (self.line == other.line and
self.column == other.column and
str(self) == str(other))
def get_line_indent(token): def get_line_indent(token):
"""Finds the indent of the line the token starts in.""" """Finds the indent of the line the token starts in."""
start = token.start_mark.buffer.rfind('\n', 0, start = token.start_mark.buffer.rfind('\n', 0,
@@ -98,35 +77,6 @@ def get_real_end_line(token):
return end_line return end_line
def get_comments_between_tokens(token1, token2, skip_first_line=False):
if token2 is None:
buf = token1.end_mark.buffer[token1.end_mark.pointer:]
elif (token1.end_mark.line == token2.start_mark.line and
not isinstance(token1, yaml.StreamStartToken) and
not isinstance(token2, yaml.StreamEndToken)):
return
else:
buf = token1.end_mark.buffer[token1.end_mark.pointer:
token2.start_mark.pointer]
line_no = token1.end_mark.line + 1
column_no = token1.end_mark.column + 1
pointer = token1.end_mark.pointer
for line in buf.split('\n'):
if skip_first_line:
skip_first_line = False
else:
pos = line.find('#')
if pos != -1:
yield Comment(line_no, column_no + pos,
token1.end_mark.buffer, pointer + pos)
pointer += len(line) + 1
line_no += 1
column_no = 1
def is_explicit_key(token): def is_explicit_key(token):
# explicit key: # explicit key:
# ? key # ? key

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**:
:: ::
@@ -84,7 +84,7 @@ TYPE = 'token'
CONF = {'present': bool} CONF = {'present': bool}
def check(conf, token, prev, next, context): def check(conf, token, prev, next, nextnext, context):
if conf['present']: if conf['present']:
if (isinstance(token, yaml.StreamEndToken) and if (isinstance(token, yaml.StreamEndToken) and
not (isinstance(prev, yaml.DocumentEndToken) or not (isinstance(prev, yaml.DocumentEndToken) or

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**:
:: ::
@@ -74,7 +74,7 @@ TYPE = 'token'
CONF = {'present': bool} CONF = {'present': bool}
def check(conf, token, prev, next, context): def check(conf, token, prev, next, nextnext, context):
if conf['present']: if conf['present']:
if (isinstance(prev, (yaml.StreamStartToken, if (isinstance(prev, (yaml.StreamStartToken,
yaml.DocumentEndToken, yaml.DocumentEndToken,

View File

@@ -62,7 +62,7 @@ CONF = {'max': int,
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 serie # Only alert on the last blank line of a series
if (line.end < len(line.buffer) - 1 and if (line.end < len(line.buffer) - 1 and
line.buffer[line.end + 1] == '\n'): line.buffer[line.end + 1] == '\n'):
return return

View File

@@ -78,7 +78,7 @@ TYPE = 'token'
CONF = {'max-spaces-after': int} CONF = {'max-spaces-after': int}
def check(conf, token, prev, next, context): def check(conf, token, prev, next, nextnext, context):
if isinstance(token, yaml.BlockEntryToken): if isinstance(token, yaml.BlockEntryToken):
problem = spaces_after(token, prev, next, problem = spaces_after(token, prev, next,
max=conf['max-spaces-after'], max=conf['max-spaces-after'],

View File

@@ -19,14 +19,18 @@ Use this rule to control the indentation.
.. rubric:: Options .. rubric:: Options
* ``spaces`` defines the number of spaces that represent an indentation level. * ``spaces`` defines the indentation width, in spaces. Set either to an integer
(e.g. ``2`` or ``4``, representing the number of spaces in an indentation
level) or to ``consistent`` to allow any number, as long as it remains the
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`` and ``whatever`` (the latter means either indenting or not indenting ``false``, ``whatever`` and ``consistent``. ``consistent`` requires either
block sequences is OK. all block sequences to be indented, or none to be. ``whatever`` means either
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
@@ -73,7 +77,29 @@ Use this rule to control the indentation.
- haystack: - haystack:
needle needle
#. With ``indentation: {spaces: 2, indent-sequences: no}`` #. With ``indentation: {spaces: consistent}``
the following code snippet would **PASS**:
::
history:
- name: Unix
date: 1969
- name: Linux
date: 1991
nest:
recurse:
- haystack:
needle
the following code snippet would **FAIL**:
::
some:
Russian:
dolls
#. With ``indentation: {spaces: 2, indent-sequences: false}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
:: ::
@@ -104,7 +130,29 @@ Use this rule to control the indentation.
- spaghetti - spaghetti
- sauce - sauce
#. With ``indentation: {spaces: 4, check-multi-line-strings: yes}`` #. With ``indentation: {spaces: 2, indent-sequences: consistent}``
the following code snippet would **PASS**:
::
- flying:
- spaghetti
- monster
- not flying:
- spaghetti
- sauce
the following code snippet would **FAIL**:
::
- flying:
- spaghetti
- monster
- not flying:
- spaghetti
- sauce
#. With ``indentation: {spaces: 4, check-multi-line-strings: true}``
the following code snippet would **PASS**: the following code snippet would **PASS**:
:: ::
@@ -150,63 +198,77 @@ from yamllint.rules.common import is_explicit_key, get_real_end_line
ID = 'indentation' ID = 'indentation'
TYPE = 'token' TYPE = 'token'
CONF = {'spaces': int, CONF = {'spaces': (int, 'consistent'),
'indent-sequences': (True, False, 'whatever'), 'indent-sequences': (bool, 'whatever', 'consistent'),
'check-multi-line-strings': bool} 'check-multi-line-strings': bool}
ROOT, MAP, B_SEQ, F_SEQ, B_ENT, KEY, VAL = range(7) 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')
class Parent(object): class Parent(object):
def __init__(self, type, indent): def __init__(self, type, indent, line_indent=None):
self.type = type self.type = type
self.indent = indent self.indent = indent
self.line_indent = line_indent
self.explicit_key = False self.explicit_key = False
self.implicit_block_seq = False
def __repr__(self):
return '%s:%d' % (labels[self.type], self.indent)
def check_scalar_indentation(conf, token, context): def check_scalar_indentation(conf, token, context):
if token.start_mark.line == token.end_mark.line: if token.start_mark.line == token.end_mark.line:
return return
if token.plain: def compute_expected_indent(found_indent):
expected_indent = token.start_mark.column def detect_indent(base_indent):
elif token.style in ('"', "'"): if type(context['spaces']) is not int:
expected_indent = token.start_mark.column + 1 context['spaces'] = found_indent - base_indent
elif token.style in ('>', '|'): return base_indent + context['spaces']
if context['stack'][-1].type == B_SEQ:
# - > if token.plain:
# multi return token.start_mark.column
# line elif token.style in ('"', "'"):
expected_indent = token.start_mark.column + conf['spaces'] return token.start_mark.column + 1
elif context['stack'][-1].type == KEY: elif token.style in ('>', '|'):
assert context['stack'][-1].explicit_key if context['stack'][-1].type == B_ENT:
# - ? > # - >
# multi-line # multi
# key # line
# : > return detect_indent(token.start_mark.column)
# multi-line elif context['stack'][-1].type == KEY:
# value assert context['stack'][-1].explicit_key
expected_indent = token.start_mark.column + conf['spaces'] # - ? >
elif context['stack'][-1].type == VAL: # multi-line
if token.start_mark.line + 1 > context['cur_line']: # key
# - key:
# >
# multi
# line
expected_indent = context['stack'][-1].indent + conf['spaces']
elif context['stack'][-2].explicit_key:
# - ? key
# : > # : >
# multi-line # multi-line
# value # value
expected_indent = token.start_mark.column + conf['spaces'] return detect_indent(token.start_mark.column)
elif context['stack'][-1].type == VAL:
if token.start_mark.line + 1 > context['cur_line']:
# - key:
# >
# multi
# line
return detect_indent(context['stack'][-1].indent)
elif context['stack'][-2].explicit_key:
# - ? key
# : >
# multi-line
# value
return detect_indent(token.start_mark.column)
else:
# - key: >
# multi
# line
return detect_indent(context['stack'][-2].indent)
else: else:
# - key: > return detect_indent(context['stack'][-1].indent)
# multi
# line expected_indent = None
expected_indent = context['stack'][-2].indent + conf['spaces']
else:
expected_indent = context['stack'][-1].indent + conf['spaces']
line_no = token.start_mark.line + 1 line_no = token.start_mark.line + 1
@@ -224,16 +286,21 @@ def check_scalar_indentation(conf, token, context):
if token.start_mark.buffer[line_start + indent] == '\n': if token.start_mark.buffer[line_start + indent] == '\n':
continue continue
if expected_indent is None:
expected_indent = compute_expected_indent(indent)
if indent != expected_indent: if indent != expected_indent:
yield LintProblem(line_no, indent + 1, yield LintProblem(line_no, indent + 1,
'wrong indentation: expected %d but found %d' % 'wrong indentation: expected %d but found %d' %
(expected_indent, indent)) (expected_indent, indent))
def check(conf, token, prev, next, context): def _check(conf, token, prev, next, nextnext, context):
if 'stack' not in context: if 'stack' not in context:
context['stack'] = [Parent(ROOT, 0)] context['stack'] = [Parent(ROOT, 0)]
context['cur_line'] = -1 context['cur_line'] = -1
context['spaces'] = conf['spaces']
context['indent-sequences'] = conf['indent-sequences']
# Step 1: Lint # Step 1: Lint
@@ -244,17 +311,22 @@ def check(conf, token, prev, next, context):
first_in_line = (is_visible and first_in_line = (is_visible and
token.start_mark.line + 1 > context['cur_line']) token.start_mark.line + 1 > context['cur_line'])
def detect_indent(base_indent, next):
if type(context['spaces']) is not int:
context['spaces'] = next.start_mark.column - base_indent
return base_indent + context['spaces']
if first_in_line: if first_in_line:
found_indentation = token.start_mark.column found_indentation = token.start_mark.column
expected = context['stack'][-1].indent expected = context['stack'][-1].indent
if isinstance(token, (yaml.FlowMappingEndToken, if isinstance(token, (yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken)): yaml.FlowSequenceEndToken)):
expected = 0 expected = context['stack'][-1].line_indent
elif (context['stack'][-1].type == KEY and elif (context['stack'][-1].type == KEY and
context['stack'][-1].explicit_key and context['stack'][-1].explicit_key and
not isinstance(token, yaml.ValueToken)): not isinstance(token, yaml.ValueToken)):
expected += conf['spaces'] expected = detect_indent(expected, token)
if found_indentation != expected: if found_indentation != expected:
yield LintProblem(token.start_mark.line + 1, found_indentation + 1, yield LintProblem(token.start_mark.line + 1, found_indentation + 1,
@@ -275,25 +347,21 @@ def check(conf, token, prev, next, context):
# Step 2.b: Update state # Step 2.b: Update state
if context['stack'][-1].type == B_ENT:
context['stack'].pop()
if isinstance(token, yaml.BlockMappingStartToken): if isinstance(token, yaml.BlockMappingStartToken):
# - a: 1
# or
# - ? a
# : 1
# or
# - ?
# a
# : 1
assert isinstance(next, yaml.KeyToken) assert isinstance(next, yaml.KeyToken)
if next.start_mark.line == token.start_mark.line: assert next.start_mark.line == token.start_mark.line
# - a: 1
# b: 2
# or
# - ? a
# : 1
indent = token.start_mark.column
else:
# - ?
# a
# : 1
indent = token.start_mark.column + conf['spaces']
context['stack'].append(Parent(MAP, indent)) indent = token.start_mark.column
context['stack'].append(Parent(B_MAP, indent))
elif isinstance(token, yaml.FlowMappingStartToken): elif isinstance(token, yaml.FlowMappingStartToken):
if next.start_mark.line == token.start_mark.line: if next.start_mark.line == token.start_mark.line:
@@ -301,17 +369,18 @@ def check(conf, token, prev, next, context):
indent = next.start_mark.column indent = next.start_mark.column
else: else:
# - { # - {
# a: 1, b: 2 # a: 1, b: 2
# } # }
indent = context['cur_line_indent'] + conf['spaces'] indent = detect_indent(context['cur_line_indent'], next)
context['stack'].append(Parent(MAP, indent)) context['stack'].append(Parent(F_MAP, indent,
line_indent=context['cur_line_indent']))
elif isinstance(token, yaml.BlockSequenceStartToken): elif isinstance(token, yaml.BlockSequenceStartToken):
# - - a # - - a
# - b # - b
assert next.start_mark.line == token.start_mark.line
assert isinstance(next, yaml.BlockEntryToken) assert isinstance(next, yaml.BlockEntryToken)
assert next.start_mark.line == token.start_mark.line
indent = token.start_mark.column indent = token.start_mark.column
@@ -320,6 +389,12 @@ def check(conf, token, prev, next, context):
elif (isinstance(token, yaml.BlockEntryToken) and elif (isinstance(token, yaml.BlockEntryToken) and
# in case of an empty entry # in case of an empty entry
not isinstance(next, (yaml.BlockEntryToken, yaml.BlockEndToken))): not isinstance(next, (yaml.BlockEntryToken, yaml.BlockEndToken))):
# It looks like pyyaml doesn't issue BlockSequenceStartTokens when the
# list is not indented. We need to compensate that.
if context['stack'][-1].type != B_SEQ:
context['stack'].append(Parent(B_SEQ, token.start_mark.column))
context['stack'][-1].implicit_block_seq = True
if next.start_mark.line == token.end_mark.line: if next.start_mark.line == token.end_mark.line:
# - item 1 # - item 1
# - item 2 # - item 2
@@ -330,7 +405,7 @@ def check(conf, token, prev, next, context):
# - # -
# key: # key:
# value # value
indent = token.start_mark.column + conf['spaces'] indent = detect_indent(token.start_mark.column, next)
context['stack'].append(Parent(B_ENT, indent)) context['stack'].append(Parent(B_ENT, indent))
@@ -342,15 +417,10 @@ def check(conf, token, prev, next, context):
# - [ # - [
# a, b # a, b
# ] # ]
indent = context['cur_line_indent'] + conf['spaces'] indent = detect_indent(context['cur_line_indent'], next)
context['stack'].append(Parent(F_SEQ, indent)) context['stack'].append(Parent(F_SEQ, indent,
line_indent=context['cur_line_indent']))
elif isinstance(token, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken)):
assert context['stack'][-1].type in (MAP, B_SEQ, F_SEQ)
context['stack'].pop()
elif isinstance(token, yaml.KeyToken): elif isinstance(token, yaml.KeyToken):
indent = context['stack'][-1].indent indent = context['stack'][-1].indent
@@ -359,21 +429,25 @@ def check(conf, token, prev, next, context):
context['stack'][-1].explicit_key = is_explicit_key(token) context['stack'][-1].explicit_key = is_explicit_key(token)
if context['stack'][-1].type == VAL:
context['stack'].pop()
assert context['stack'][-1].type == KEY
context['stack'].pop()
elif isinstance(token, yaml.ValueToken): elif isinstance(token, yaml.ValueToken):
assert context['stack'][-1].type == KEY assert context['stack'][-1].type == KEY
# Discard empty values # Special cases:
if isinstance(next, (yaml.BlockEndToken, # key: &anchor
yaml.FlowMappingEndToken, # value
yaml.FlowSequenceEndToken, # and:
yaml.KeyToken)): # key: !!tag
context['stack'].pop() # value
else: if isinstance(next, (yaml.AnchorToken, yaml.TagToken)):
if (next.start_mark.line == prev.start_mark.line and
next.start_mark.line < nextnext.start_mark.line):
next = nextnext
# Only if value is not empty
if not isinstance(next, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken,
yaml.KeyToken)):
if context['stack'][-1].explicit_key: if context['stack'][-1].explicit_key:
# ? k # ? k
# : value # : value
@@ -381,7 +455,7 @@ def check(conf, token, prev, next, context):
# ? k # ? k
# : # :
# value # value
indent = context['stack'][-1].indent + conf['spaces'] indent = detect_indent(context['stack'][-1].indent, next)
elif next.start_mark.line == prev.start_mark.line: elif next.start_mark.line == prev.start_mark.line:
# k: value # k: value
indent = next.start_mark.column indent = next.start_mark.column
@@ -392,33 +466,103 @@ def check(conf, token, prev, next, context):
# yaml.scan()ning this: # yaml.scan()ning this:
# '- lib:\n' # '- lib:\n'
# ' - var\n' # ' - var\n'
if conf['indent-sequences'] is False: if context['indent-sequences'] is False:
indent = context['stack'][-1].indent indent = context['stack'][-1].indent
elif conf['indent-sequences'] is True: elif context['indent-sequences'] is True:
indent = context['stack'][-1].indent + conf['spaces'] if (context['spaces'] == 'consistent' and
else: # 'whatever' 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'
if next.start_mark.column == context['stack'][-1].indent: if next.start_mark.column == context['stack'][-1].indent:
# key: # key:
# - e1 # - e1
# - e2 # - e2
if context['indent-sequences'] == 'consistent':
context['indent-sequences'] = False
indent = context['stack'][-1].indent indent = context['stack'][-1].indent
else: else:
if context['indent-sequences'] == 'consistent':
context['indent-sequences'] = True
# key: # key:
# - e1 # - e1
# - e2 # - e2
indent = context['stack'][-1].indent + conf['spaces'] indent = detect_indent(context['stack'][-1].indent,
next)
else: else:
# k: # k:
# value # value
indent = context['stack'][-1].indent + conf['spaces'] indent = detect_indent(context['stack'][-1].indent, next)
context['stack'].append(Parent(VAL, indent)) context['stack'].append(Parent(VAL, indent))
if (context['stack'][-1].type == KEY and consumed_current_token = False
isinstance(next, (yaml.BlockEndToken, while True:
yaml.FlowMappingEndToken, if (context['stack'][-1].type == F_SEQ and
yaml.FlowSequenceEndToken, isinstance(token, yaml.FlowSequenceEndToken) and
yaml.KeyToken))): not consumed_current_token):
# A key without a value: it's part of a set. Let's drop this key context['stack'].pop()
# and leave room for the next one. consumed_current_token = True
context['stack'].pop()
elif (context['stack'][-1].type == F_MAP and
isinstance(token, yaml.FlowMappingEndToken) and
not consumed_current_token):
context['stack'].pop()
consumed_current_token = True
elif (context['stack'][-1].type in (B_MAP, B_SEQ) and
isinstance(token, yaml.BlockEndToken) and
not context['stack'][-1].implicit_block_seq and
not consumed_current_token):
context['stack'].pop()
consumed_current_token = True
elif (context['stack'][-1].type == B_ENT and
not isinstance(token, yaml.BlockEntryToken) and
context['stack'][-2].implicit_block_seq and
not isinstance(token, (yaml.AnchorToken, yaml.TagToken)) and
not isinstance(next, yaml.BlockEntryToken)):
context['stack'].pop()
context['stack'].pop()
elif (context['stack'][-1].type == B_ENT and
isinstance(next, (yaml.BlockEntryToken, yaml.BlockEndToken))):
context['stack'].pop()
elif (context['stack'][-1].type == VAL and
not isinstance(token, yaml.ValueToken) and
not isinstance(token, (yaml.AnchorToken, yaml.TagToken))):
assert context['stack'][-2].type == KEY
context['stack'].pop()
context['stack'].pop()
elif (context['stack'][-1].type == KEY and
isinstance(next, (yaml.BlockEndToken,
yaml.FlowMappingEndToken,
yaml.FlowSequenceEndToken,
yaml.KeyToken))):
# A key without a value: it's part of a set. Let's drop this key
# and leave room for the next one.
context['stack'].pop()
else:
break
def check(conf, token, prev, next, nextnext, context):
try:
for problem in _check(conf, token, prev, next, nextnext, context):
yield problem
except AssertionError:
yield LintProblem(token.start_mark.line + 1,
token.start_mark.column + 1,
'cannot infer indentation: unexpected token')

View File

@@ -72,7 +72,7 @@ class Parent(object):
self.keys = [] self.keys = []
def check(conf, token, prev, next, context): def check(conf, token, prev, next, nextnext, context):
if 'stack' not in context: if 'stack' not in context:
context['stack'] = [] context['stack'] = []

View File

@@ -22,7 +22,9 @@ Use this rule to set a limit to lines length.
* ``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``
and extends it to also allow non-breakable words in inline mappings.
.. rubric:: Examples .. rubric:: Examples
@@ -42,7 +44,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**:
:: ::
@@ -59,9 +61,22 @@ Use this rule to set a limit to lines length.
the following code snippet would **FAIL**: the following code snippet would **FAIL**:
:: ::
- this line is waaaaaaaaaaaaaay too long but could be easily splitted... - this line is waaaaaaaaaaaaaay too long but could be easily split...
#. With ``line-length: {max: 60, allow-non-breakable-words: no}`` and the following code snippet would also **FAIL**:
::
- foobar: http://localhost/very/very/very/very/very/very/very/very/long/url
#. With ``line-length: {max: 60, allow-non-breakable-words: true,
allow-non-breakable-inline-mappings: true}``
the following code snippet would **PASS**:
::
- foobar: http://localhost/very/very/very/very/very/very/very/very/long/url
#. With ``line-length: {max: 60, allow-non-breakable-words: false}``
the following code snippet would **FAIL**: the following code snippet would **FAIL**:
:: ::
@@ -73,29 +88,55 @@ Use this rule to set a limit to lines length.
""" """
import yaml
from yamllint.linter import LintProblem from yamllint.linter import LintProblem
ID = 'line-length' ID = 'line-length'
TYPE = 'line' TYPE = 'line'
CONF = {'max': int, CONF = {'max': int,
'allow-non-breakable-words': bool} 'allow-non-breakable-words': bool,
'allow-non-breakable-inline-mappings': bool}
def check_inline_mapping(line):
loader = yaml.SafeLoader(line.content)
try:
while loader.peek_token():
if isinstance(loader.get_token(), yaml.BlockMappingStartToken):
while loader.peek_token():
if isinstance(loader.get_token(), yaml.ValueToken):
t = loader.get_token()
if isinstance(t, yaml.ScalarToken):
return (
' ' not in line.content[t.start_mark.column:])
except yaml.scanner.ScannerError:
pass
return False
def check(conf, line): def check(conf, line):
if line.end - line.start > conf['max']: if line.end - line.start > conf['max']:
conf['allow-non-breakable-words'] |= \
conf['allow-non-breakable-inline-mappings']
if conf['allow-non-breakable-words']: if conf['allow-non-breakable-words']:
start = line.start start = line.start
while start < line.end and line.buffer[start] == ' ': while start < line.end and line.buffer[start] == ' ':
start += 1 start += 1
if start != line.end: if start != line.end:
if line.buffer[start] == '#': if line.buffer[start] in ('#', '-'):
start += 2 start += 2
if line.buffer.find(' ', start, line.end) == -1: if line.buffer.find(' ', start, line.end) == -1:
return return
if (conf['allow-non-breakable-inline-mappings'] and
check_inline_mapping(line)):
return
yield LintProblem(line.line_no, conf['max'] + 1, yield LintProblem(line.line_no, conf['max'] + 1,
'line too long (%d > %d characters)' % 'line too long (%d > %d characters)' %
(line.end - line.start, conf['max'])) (line.end - line.start, conf['max']))

93
yamllint/rules/truthy.py Normal file
View File

@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2016 Peter Ericson
#
# 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 truthy values that are not quoted nor explicitly typed.
This would prevent YAML parsers from transforming ``[yes, FALSE, Off]`` into
``[true, false, false]`` or ``{y: 1, yes: 2, on: 3, true: 4, True: 5}`` into
``{y: 1, true: 5}``.
.. rubric:: Examples
#. With ``truthy: {}``
the following code snippet would **PASS**:
::
boolean: true
object: {"True": 1, 1: "True"}
"yes": 1
"on": 2
"true": 3
"True": 4
explicit:
string1: !!str True
string2: !!str yes
string3: !!str off
encoded: !!binary |
True
OFF
pad== # this decodes as 'N\xbb\x9e8Qii'
boolean1: !!bool true
boolean2: !!bool "false"
boolean3: !!bool FALSE
boolean4: !!bool True
boolean5: !!bool off
boolean6: !!bool NO
the following code snippet would **FAIL**:
::
object: {True: 1, 1: True}
the following code snippet would **FAIL**:
::
yes: 1
on: 2
true: 3
True: 4
"""
import yaml
from yamllint.linter import LintProblem
ID = 'truthy'
TYPE = 'token'
CONF = {}
TRUTHY = ['YES', 'Yes', 'yes',
'NO', 'No', 'no',
'TRUE', 'True', # 'true' is a boolean
'FALSE', 'False', # 'false' is a boolean
'ON', 'On', 'on',
'OFF', 'Off', 'off']
def check(conf, token, prev, next, nextnext, context):
if prev and isinstance(prev, yaml.tokens.TagToken):
return
if isinstance(token, yaml.tokens.ScalarToken):
if token.value in TRUTHY and token.style is None:
yield LintProblem(token.start_mark.line + 1,
token.start_mark.column + 1,
"truthy value is not quoted")