From f16340e99a2dbd2187a0e547256296717d1196d6 Mon Sep 17 00:00:00 2001 From: hirosassa Date: Tue, 13 Jul 2021 10:00:32 +0900 Subject: support ignore config in pyproject.toml (#937) * support ignore config in pyproject.toml * add doc in readme * add tests * changelog --- CHANGELOG | 5 +++ README.rst | 15 ++++++-- yapf/yapflib/file_resources.py | 44 +++++++++++++++++++--- yapftests/file_resources_test.py | 81 +++++++++++++++++++++++++++++++++++++--- 4 files changed, 132 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index afe8718..a35ce68 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,11 @@ # All notable changes to this project will be documented in this file. # This project adheres to [Semantic Versioning](http://semver.org/). +## [0.32.0] UNRELEASED +### Added +- Look at the 'pyproject.toml' file to see if it contains ignore file information + for YAPF. + ## [0.31.0] 2021-03-14 ### Added - Renamed 'master' brannch to 'main'. diff --git a/README.rst b/README.rst index 1bc7b58..b381c4d 100644 --- a/README.rst +++ b/README.rst @@ -136,12 +136,12 @@ otherwise (including program error). You can use this in a CI workflow to test t has been YAPF-formatted. --------------------------------------------- -Excluding files from formatting (.yapfignore) +Excluding files from formatting (.yapfignore or pyproject.toml) --------------------------------------------- In addition to exclude patterns provided on commandline, YAPF looks for additional -patterns specified in a file named ``.yapfignore`` located in the working directory from -which YAPF is invoked. +patterns specified in a file named ``.yapfignore`` or ``pyproject.toml`` located in the +working directory from which YAPF is invoked. ``.yapfignore``'s syntax is similar to UNIX's filename pattern matching:: @@ -152,6 +152,15 @@ which YAPF is invoked. Note that no entry should begin with `./`. +If you use ``pyproject.toml``, exclude patterns are specified by ``ignore_pattens`` key +in ``[tool.yapfignore]`` section. For example: + +.. code-block:: ini + [tool.yapfignore] + ignore_patterns=""" + temp/**/*.py + temp2/*.py + """ Formatting style ================ diff --git a/yapf/yapflib/file_resources.py b/yapf/yapflib/file_resources.py index 84a8a54..4c0fbf2 100644 --- a/yapf/yapflib/file_resources.py +++ b/yapf/yapflib/file_resources.py @@ -32,10 +32,9 @@ LF = '\n' CRLF = '\r\n' -def _GetExcludePatternsFromFile(filename): - """Get a list of file patterns to ignore.""" +def _GetExcludePatternsFromYapfIgnore(filename): + """Get a list of file patterns to ignore from .yapfignore.""" ignore_patterns = [] - # See if we have a .yapfignore file. if os.path.isfile(filename) and os.access(filename, os.R_OK): with open(filename, 'r') as fd: for line in fd: @@ -48,6 +47,33 @@ def _GetExcludePatternsFromFile(filename): return ignore_patterns +def _GetExcludePatternsFromPyprojectToml(filename): + """Get a list of file patterns to ignore from pyproject.toml.""" + ignore_patterns = [] + try: + import toml + except ImportError: + raise errors.YapfError( + "toml package is needed for using pyproject.toml as a configuration file" + ) + + pyproject_toml = toml.load(filename) + if os.path.isfile(filename) and os.access(filename, os.R_OK): + excludes = pyproject_toml.get('tool', + {}).get('yapfignore', + {}).get('ignore_patterns', None) + if excludes is None: + return [] + + for line in excludes.split('\n'): + if line.strip() and not line.startswith('#'): + ignore_patterns.append(line.strip()) + if any(e.startswith('./') for e in ignore_patterns): + raise errors.YapfError('path in pyproject.toml should not start with ./') + + return ignore_patterns + + def GetExcludePatternsForDir(dirname): """Return patterns of files to exclude from ignorefile in a given directory. @@ -60,8 +86,16 @@ def GetExcludePatternsForDir(dirname): A List of file patterns to exclude if ignore file is found, otherwise empty List. """ - ignore_file = os.path.join(dirname, '.yapfignore') - return _GetExcludePatternsFromFile(ignore_file) + ignore_patterns = [] + + yapfignore_file = os.path.join(dirname, '.yapfignore') + if os.path.exists(yapfignore_file): + ignore_patterns += _GetExcludePatternsFromYapfIgnore(yapfignore_file) + + pyproject_toml_file = os.path.join(dirname, 'pyproject.toml') + if os.path.exists(pyproject_toml_file): + ignore_patterns += _GetExcludePatternsFromPyprojectToml(pyproject_toml_file) + return ignore_patterns def GetDefaultStyleForDir(dirname, default_style=style.DEFAULT_STYLE): diff --git a/yapftests/file_resources_test.py b/yapftests/file_resources_test.py index a4b1230..6afd8a4 100644 --- a/yapftests/file_resources_test.py +++ b/yapftests/file_resources_test.py @@ -54,16 +54,87 @@ class GetExcludePatternsForDir(unittest.TestCase): def tearDown(self): # pylint: disable=g-missing-super-call shutil.rmtree(self.test_tmpdir) - def _make_test_dir(self, name): - fullpath = os.path.normpath(os.path.join(self.test_tmpdir, name)) - os.makedirs(fullpath) - return fullpath + def test_get_exclude_file_patterns_from_yapfignore(self): + local_ignore_file = os.path.join(self.test_tmpdir, '.yapfignore') + ignore_patterns = ['temp/**/*.py', 'temp2/*.py'] + with open(local_ignore_file, 'w') as f: + f.writelines('\n'.join(ignore_patterns)) - def test_get_exclude_file_patterns(self): + self.assertEqual( + sorted(file_resources.GetExcludePatternsForDir(self.test_tmpdir)), + sorted(ignore_patterns)) + + def test_get_exclude_file_patterns_from_yapfignore_with_wrong_syntax(self): local_ignore_file = os.path.join(self.test_tmpdir, '.yapfignore') + ignore_patterns = ['temp/**/*.py', './wrong/syntax/*.py'] + with open(local_ignore_file, 'w') as f: + f.writelines('\n'.join(ignore_patterns)) + + with self.assertRaises(errors.YapfError): + file_resources.GetExcludePatternsForDir(self.test_tmpdir) + + def test_get_exclude_file_patterns_from_pyproject(self): + try: + import toml + except ImportError: + return + local_ignore_file = os.path.join(self.test_tmpdir, 'pyproject.toml') ignore_patterns = ['temp/**/*.py', 'temp2/*.py'] with open(local_ignore_file, 'w') as f: + f.write('[tool.yapfignore]\n') + f.write('ignore_patterns="""') + f.writelines('\n'.join(ignore_patterns)) + f.write('"""') + + self.assertEqual( + sorted(file_resources.GetExcludePatternsForDir(self.test_tmpdir)), + sorted(ignore_patterns)) + + def test_get_exclude_file_patterns_from_pyproject_with_wrong_syntax(self): + try: + import toml + except ImportError: + return + local_ignore_file = os.path.join(self.test_tmpdir, 'pyproject.toml') + ignore_patterns = ['temp/**/*.py', './wrong/syntax/*.py'] + with open(local_ignore_file, 'w') as f: + f.write('[tool.yapfignore]\n') + f.write('ignore_patterns="""') f.writelines('\n'.join(ignore_patterns)) + f.write('"""') + + with self.assertRaises(errors.YapfError): + file_resources.GetExcludePatternsForDir(self.test_tmpdir) + + def test_get_exclude_file_patterns_from_pyproject_no_ignore_section(self): + try: + import toml + except ImportError: + return + local_ignore_file = os.path.join(self.test_tmpdir, 'pyproject.toml') + ignore_patterns = [] + open(local_ignore_file, 'w').close() + + self.assertEqual( + sorted(file_resources.GetExcludePatternsForDir(self.test_tmpdir)), + sorted(ignore_patterns)) + + def test_get_exclude_file_patterns_from_pyproject_ignore_section_empty(self): + try: + import toml + except ImportError: + return + local_ignore_file = os.path.join(self.test_tmpdir, 'pyproject.toml') + ignore_patterns = [] + with open(local_ignore_file, 'w') as f: + f.write('[tool.yapfignore]\n') + + self.assertEqual( + sorted(file_resources.GetExcludePatternsForDir(self.test_tmpdir)), + sorted(ignore_patterns)) + + def test_get_exclude_file_patterns_with_no_config_files(self): + ignore_patterns = [] self.assertEqual( sorted(file_resources.GetExcludePatternsForDir(self.test_tmpdir)), -- cgit v1.2.3