diff --git a/.travis.yml b/.travis.yml
index c40c6bd..f27f4bb 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,6 +14,9 @@ install:
- pip install .
script:
- if [[ $TRAVIS_PYTHON_VERSION != 2.6 ]]; then flake8 .; fi
+ # Because of https://github.com/PyCQA/flake8-import-order/issues/149
+ # otherwise tests fail with Python 3.6:
+ - pip uninstall --yes enum34
- yamllint --strict $(git ls-files '*.yaml' '*.yml')
- coverage run --source=yamllint setup.py test
- if [[ $TRAVIS_PYTHON_VERSION != 2* ]]; then
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index bc1fec4..1127c82 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,11 @@
Changelog
=========
+1.11.0 (2018-02-21)
+-------------------
+
+- Add a new `octal-values` rule
+
1.10.0 (2017-11-05)
-------------------
diff --git a/docs/rules.rst b/docs/rules.rst
index 24318c7..3900c28 100644
--- a/docs/rules.rst
+++ b/docs/rules.rst
@@ -99,6 +99,11 @@ new-lines
.. automodule:: yamllint.rules.new_lines
+octal-values
+------------
+
+.. automodule:: yamllint.rules.octal_values
+
trailing-spaces
---------------
diff --git a/setup.cfg b/setup.cfg
index 82ba8ec..bd753fb 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,6 +3,7 @@ universal = 1
[flake8]
import-order-style = pep8
+application-import-names = yamllint
[build_sphinx]
all-files = 1
diff --git a/tests/rules/test_indentation.py b/tests/rules/test_indentation.py
index 374f23c..3632a25 100644
--- a/tests/rules/test_indentation.py
+++ b/tests/rules/test_indentation.py
@@ -15,6 +15,7 @@
# along with this program. If not, see .
from tests.common import RuleTestCase
+
from yamllint.parser import token_or_comment_generator, Comment
from yamllint.rules.indentation import check
diff --git a/tests/rules/test_octal_values.py b/tests/rules/test_octal_values.py
new file mode 100644
index 0000000..7e82d4e
--- /dev/null
+++ b/tests/rules/test_octal_values.py
@@ -0,0 +1,72 @@
+# -*- 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 .
+
+from tests.common import RuleTestCase
+
+
+class OctalValuesTestCase(RuleTestCase):
+ rule_id = 'octal-values'
+
+ def test_disabled(self):
+ conf = ('octal-values: disable\n'
+ 'new-line-at-end-of-file: disable\n'
+ 'document-start: disable\n')
+ self.check('user-city: 010', conf)
+ self.check('user-city: 0o10', conf)
+
+ def test_implicit_octal_values(self):
+ conf = ('octal-values: {forbid-implicit-octal: true}\n'
+ 'new-line-at-end-of-file: disable\n'
+ 'document-start: disable\n')
+ self.check('user-city: 010', conf, problem=(1, 15))
+ self.check('user-city: abc', conf)
+ self.check('user-city: 010,0571', conf)
+ self.check("user-city: '010'", conf)
+ self.check('user-city: "010"', conf)
+ self.check('user-city:\n'
+ ' - 010', conf, problem=(2, 8))
+ self.check('user-city: [010]', conf, problem=(1, 16))
+ self.check('user-city: {beijing: 010}', conf, problem=(1, 25))
+ self.check('explicit-octal: 0o10', conf)
+ self.check('not-number: 0abc', conf)
+ self.check('zero: 0', conf)
+ self.check('hex-value: 0x10', conf)
+ self.check('number-values:\n'
+ ' - 0.10\n'
+ ' - .01\n'
+ ' - 0e3\n', conf)
+
+ def test_explicit_octal_values(self):
+ conf = ('octal-values: {forbid-explicit-octal: true}\n'
+ 'new-line-at-end-of-file: disable\n'
+ 'document-start: disable\n')
+ self.check('user-city: 0o10', conf, problem=(1, 16))
+ self.check('user-city: abc', conf)
+ self.check('user-city: 0o10,0571', conf)
+ self.check("user-city: '0o10'", conf)
+ self.check('user-city:\n'
+ ' - 0o10', conf, problem=(2, 9))
+ self.check('user-city: [0o10]', conf, problem=(1, 17))
+ self.check('user-city: {beijing: 0o10}', conf, problem=(1, 26))
+ self.check('implicit-octal: 010', conf)
+ self.check('not-number: 0oabc', conf)
+ self.check('zero: 0', conf)
+ self.check('hex-value: 0x10', conf)
+ self.check('number-values:\n'
+ ' - 0.10\n'
+ ' - .01\n'
+ ' - 0e3\n', conf)
+ self.check('user-city: "010"', conf)
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 47b5daf..341e3fc 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -30,10 +30,10 @@ try:
except AssertionError:
import unittest2 as unittest
-from yamllint import cli
-
from tests.common import build_temp_workspace
+from yamllint import cli
+
@unittest.skipIf(sys.version_info < (2, 7), 'Python 2.6 not supported')
class CommandLineTestCase(unittest.TestCase):
diff --git a/tests/test_config.py b/tests/test_config.py
index 7e719b1..3b6b9c2 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -27,11 +27,11 @@ try:
except AssertionError:
import unittest2 as unittest
+from tests.common import build_temp_workspace
+
from yamllint import cli
from yamllint import config
-from tests.common import build_temp_workspace
-
class SimpleConfigTestCase(unittest.TestCase):
def test_parse_config(self):
diff --git a/yamllint/__init__.py b/yamllint/__init__.py
index 8ff52f9..f86c4ea 100644
--- a/yamllint/__init__.py
+++ b/yamllint/__init__.py
@@ -22,7 +22,7 @@ indentation, etc."""
APP_NAME = 'yamllint'
-APP_VERSION = '1.10.0'
+APP_VERSION = '1.11.0'
APP_DESCRIPTION = __doc__
__author__ = u'Adrien Vergé'
diff --git a/yamllint/conf/default.yaml b/yamllint/conf/default.yaml
index 8c0e89a..8432c9a 100644
--- a/yamllint/conf/default.yaml
+++ b/yamllint/conf/default.yaml
@@ -50,6 +50,9 @@ rules:
new-line-at-end-of-file: enable
new-lines:
type: unix
+ octal-values:
+ forbid-implicit-octal: false
+ forbid-explicit-octal: false
trailing-spaces: enable
truthy:
level: warning
diff --git a/yamllint/rules/__init__.py b/yamllint/rules/__init__.py
index 189ec69..d08a326 100644
--- a/yamllint/rules/__init__.py
+++ b/yamllint/rules/__init__.py
@@ -32,6 +32,7 @@ from yamllint.rules import (
line_length,
new_line_at_end_of_file,
new_lines,
+ octal_values,
trailing_spaces,
truthy,
)
@@ -54,6 +55,7 @@ _RULES = {
line_length.ID: line_length,
new_line_at_end_of_file.ID: new_line_at_end_of_file,
new_lines.ID: new_lines,
+ octal_values.ID: octal_values,
trailing_spaces.ID: trailing_spaces,
truthy.ID: truthy,
}
diff --git a/yamllint/rules/octal_values.py b/yamllint/rules/octal_values.py
new file mode 100644
index 0000000..33c0793
--- /dev/null
+++ b/yamllint/rules/octal_values.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2017 ScienJus
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+"""
+Use this rule to prevent values with octal numbers. In YAML, numbers that
+start with ``0`` are interpreted as octal, but this is not always wanted.
+For instance ``010`` is the city code of Beijing, and should not be
+converted to ``8``.
+
+.. rubric:: Examples
+
+#. With ``octal-values: {forbid-implicit-octal: true}``
+
+ the following code snippets would **PASS**:
+ ::
+
+ user:
+ city-code: '010'
+
+ the following code snippets would **PASS**:
+ ::
+
+ user:
+ city-code: 010,021
+
+ the following code snippets would **FAIL**:
+ ::
+
+ user:
+ city-code: 010
+
+#. With ``octal-values: {forbid-explicit-octal: true}``
+
+ the following code snippets would **PASS**:
+ ::
+
+ user:
+ city-code: '0o10'
+
+ the following code snippets would **FAIL**:
+ ::
+
+ user:
+ city-code: 0o10
+"""
+
+import yaml
+
+from yamllint.linter import LintProblem
+
+
+ID = 'octal-values'
+TYPE = 'token'
+CONF = {'forbid-implicit-octal': bool,
+ 'forbid-explicit-octal': bool}
+
+
+def check(conf, token, prev, next, nextnext, context):
+ if prev and isinstance(prev, yaml.tokens.TagToken):
+ return
+
+ if conf['forbid-implicit-octal']:
+ if isinstance(token, yaml.tokens.ScalarToken):
+ if not token.style:
+ val = token.value
+ if val.isdigit() and len(val) > 1 and val[0] == '0':
+ yield LintProblem(
+ token.start_mark.line + 1, token.end_mark.column + 1,
+ 'forbidden implicit octal value "%s"' %
+ token.value)
+
+ if conf['forbid-explicit-octal']:
+ if isinstance(token, yaml.tokens.ScalarToken):
+ if not token.style:
+ val = token.value
+ if len(val) > 2 and val[:2] == '0o' and val[2:].isdigit():
+ yield LintProblem(
+ token.start_mark.line + 1, token.end_mark.column + 1,
+ 'forbidden explicit octal value "%s"' %
+ token.value)