Repository: google/yapf

Issue:
Ignore files
It was great if one could add files to ignore to a knobs config. This will apply for recursive mode. Sometimes there are auto generated files where it makes no sense to improve the style.

Discussion:
googlebot: 
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

:memo: **Please visit <https://cla.developers.google.com/> to sign.**

Once you've signed (or fixed any issues), please reply here (e.g. `I signed it!`) and we'll verify it.

----

#### What to do if you already signed the CLA

##### Individual signers

*   It's possible we don't have your GitHub username or you're using a different email address on your commit. Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).

##### Corporate signers

*   Your company has a Point of Contact who decides which employees are authorized to participate. Ask your POC to be added to the group of authorized contributors. If you don't know who your Point of Contact is, direct the project maintainer to `go/cla#troubleshoot`.
*   The email used to register you as an authorized contributor must be the email used for the Git commit. Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).
*   The email used to register you as an authorized contributor must also be [attached to your GitHub account](https://github.com/settings/emails).
		

<!-- need_sender_cla -->
coveralls: 
[![Coverage Status](https://coveralls.io/builds/16158749/badge)](https://coveralls.io/builds/16158749)

Coverage decreased (-0.01%) to 95.51% when pulling **e7c0a5f6e640b3eab0ef3e33e1c604eff394662c on bj00rn:master** into **8d90c204e8abecc59d8e9a6037782293f26fa7f8 on google:master**.

bj00rn: @gwelymernans will you look at this?
softinio: @bj00rn Is this PR active? Likely to be approved? Looks like you need to sign CLA.
gwelymernans: I'm sorry about the delay. It's been busy.

Could you please update `CHANGELOG` and `README.rst`. Also, you need to sign the CLA for me to accept this.
bj00rn: I signed it!
googlebot: CLAs look good, thanks!

<!-- ok -->
bj00rn: @gwelymernans updated & signed!
bcollazo: Anything new on this front? Looking forward to using the feature! Thanks.
gwelymernans: Please resolve the merge conflicts as well.

Patch:
diff --git a/CHANGELOG b/CHANGELOG
index 698e6ea..504fcde 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,10 @@
 # All notable changes to this project will be documented in this file.
 # This project adheres to [Semantic Versioning](http://semver.org/).
 
+## [0.26.0] UNRELEASED
+### Added
+- Support additional file exclude patterns in .yapfignore file.
+
 ## [0.25.0] UNRELEASED
 ### Added
 - Added `INDENT_BLANK_LINES` knob to select whether the blank lines are empty
diff --git a/README.rst b/README.rst
index 366a80e..f5773e0 100644
--- a/README.rst
+++ b/README.rst
@@ -136,6 +136,14 @@ If ``--diff`` is supplied, YAPF returns zero when no changes were necessary, non
 otherwise (including program error). You can use this in a CI workflow to test that code
 has been YAPF-formatted.
 
+---------------------------------------------
+Excluding files from formatting (.yapfignore)
+---------------------------------------------
+
+In addition to exlude 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.
+
 
 Formatting style
 ================
diff --git a/yapf/__init__.py b/yapf/__init__.py
index c6ffc4d..bda5b81 100644
--- a/yapf/__init__.py
+++ b/yapf/__init__.py
@@ -188,8 +188,13 @@ def main(argv):
     file_resources.WriteReformattedCode('<stdout>', reformatted_source)
     return 0
 
-  files = file_resources.GetCommandLineFiles(args.files, args.recursive,
-                                             args.exclude)
+  # Get additional exclude patterns from ignorefile
+  exclude_patterns_from_ignore_file = file_resources.GetExcludePatternsForDir(
+      os.getcwd())
+
+  files = file_resources.GetCommandLineFiles(
+      args.files, args.recursive,
+      (args.exclude or []) + exclude_patterns_from_ignore_file)
   if not files:
     raise errors.YapfError('Input filenames did not match any python files')
 
diff --git a/yapf/yapflib/file_resources.py b/yapf/yapflib/file_resources.py
index 6e7202d..18e6c6b 100644
--- a/yapf/yapflib/file_resources.py
+++ b/yapf/yapflib/file_resources.py
@@ -32,6 +32,36 @@ LF = '\n'
 CRLF = '\r\n'
 
 
+def _GetExcludePatternsFromFile(filename):
+  ignore_patterns = []
+  # See if we have a .yapfignore file.
+  if os.path.isfile(filename) and os.access(filename, os.R_OK):
+    for line in open(filename, 'r').readlines():
+      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 .yapfignore should not start with ./')
+
+  return ignore_patterns
+
+
+def GetExcludePatternsForDir(dirname):
+  """Return patterns of files to exclude from ignorefile in a given directory.
+
+   Looks for .yapfignore in the directory dirname.
+
+   Arguments:
+     dirname: (unicode) The name of the directory.
+
+   Returns:
+     A List of file patterns to exclude if ignore file is found
+     , otherwhise empty List.
+   """
+  ignore_file = os.path.join(dirname, '.yapfignore')
+  return _GetExcludePatternsFromFile(ignore_file)
+
+
 def GetDefaultStyleForDir(dirname):
   """Return default style name for a given directory.
 


Test patch:
diff --git a/yapftests/file_resources_test.py b/yapftests/file_resources_test.py
index 8391e5f..0f413f8 100644
--- a/yapftests/file_resources_test.py
+++ b/yapftests/file_resources_test.py
@@ -36,6 +36,30 @@ def _restore_working_dir():
     os.chdir(curdir)
 
 
+class GetExcludePatternsForDir(unittest.TestCase):
+
+  def setUp(self):
+    self.test_tmpdir = tempfile.mkdtemp()
+
+  def tearDown(self):
+    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(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))
+
+    self.assertEqual(
+        sorted(file_resources.GetExcludePatternsForDir(self.test_tmpdir)),
+        sorted(ignore_patterns))
+
+
 class GetDefaultStyleForDirTest(unittest.TestCase):
 
   def setUp(self):