Repository: google/yapf

Issue:
yapf crashes with misggin style (e.g. --style=foo)
I've got a fix for this that I will send a PR for.

This is  the crash:
```
PYTHONPATH=$PWD/yapf python -m yapf --style=foo -i -r . 
Traceback (most recent call last):
  File "/usr/lib/python2.7/runpy.py", line 162, in _run_module_as_main
    "__main__", fname, loader, pkg_name)
  File "/usr/lib/python2.7/runpy.py", line 72, in _run_code
    exec code in run_globals
  File "/usr/local/google/home/sbc/dev/yapf/yapf/__main__.py", line 16, in <module>
    yapf.run_main()
  File "yapf/__init__.py", line 209, in run_main
    sys.exit(main(sys.argv))
  File "yapf/__init__.py", line 146, in main
    verify=args.verify)
  File "yapf/__init__.py", line 174, in FormatFiles
    print_diff=print_diff, verify=verify)
  File "yapf/yapflib/yapf_api.py", line 78, in FormatFile
    verify=verify), encoding
  File "yapf/yapflib/yapf_api.py", line 100, in FormatCode
    style.SetGlobalStyle(style.CreateStyleFromConfig(style_config))
  File "yapf/yapflib/style.py", line 212, in CreateStyleFromConfig
    config = _CreateConfigParserFromConfigFile(style_config)
  File "yapf/yapflib/style.py", line 234, in _CreateConfigParserFromConfigFile
    '"{0}" is not a valid style or file path'.format(config_filename))
yapf.yapflib.style.StyleConfigError: "foo" is not a valid style or file path
```


Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/2434186/badge)](https://coveralls.io/builds/2434186)

Coverage decreased (-0.2%) to 91.89% when pulling **53c06d00461e35cfd3ae9a27cecc7dcc9d0912f0 on sbc100:handle_errors** into **8c7840c8b835568116219e9d960764198add4927 on google:master**.


Patch:
diff --git a/yapf/__init__.py b/yapf/__init__.py
index 7634c5c..7ffdfee 100644
--- a/yapf/__init__.py
+++ b/yapf/__init__.py
@@ -32,6 +32,7 @@ import logging
 import os
 import sys
 
+from yapf.yapflib import errors
 from yapf.yapflib import file_resources
 from yapf.yapflib import py3compat
 from yapf.yapflib import style
@@ -40,10 +41,6 @@ from yapf.yapflib import yapf_api
 __version__ = '0.1.7'
 
 
-class YapfError(Exception):
-  pass
-
-
 def main(argv):
   """Main program.
 
@@ -147,7 +144,7 @@ def main(argv):
 
   files = file_resources.GetCommandLineFiles(args.files, args.recursive)
   if not files:
-    raise YapfError('Input filenames did not match any python files')
+    raise errors.YapfError('Input filenames did not match any python files')
   FormatFiles(files, lines,
               style_config=args.style,
               no_local_style=args.no_local_style,
@@ -213,15 +210,19 @@ def _GetLines(line_strings):
     # The 'list' here is needed by Python 3.
     line = list(map(int, line_string.split('-', 1)))
     if line[0] < 1:
-      raise ValueError('invalid start of line range: %r' % line)
+      raise errors.YapfError('invalid start of line range: %r' % line)
     if line[0] > line[1]:
-      raise ValueError('end comes before start in line range: %r', line)
+      raise errors.YapfError('end comes before start in line range: %r', line)
     lines.append(tuple(line))
   return lines
 
 
 def run_main():  # pylint: disable=invalid-name
-  sys.exit(main(sys.argv))
+  try:
+    sys.exit(main(sys.argv))
+  except errors.YapfError as e:
+    sys.stderr.write('yapf: ' + str(e) + '\n')
+    sys.exit(1)
 
 
 if __name__ == '__main__':
diff --git a/yapf/yapflib/__init__.py b/yapf/yapflib/__init__.py
index e7522b2..f526bc3 100644
--- a/yapf/yapflib/__init__.py
+++ b/yapf/yapflib/__init__.py
@@ -11,3 +11,4 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+
diff --git a/yapf/yapflib/errors.py b/yapf/yapflib/errors.py
new file mode 100644
index 0000000..1bf5bc7
--- /dev/null
+++ b/yapf/yapflib/errors.py
@@ -0,0 +1,22 @@
+# Copyright 2015 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+class YapfError(Exception):
+  """Parent class for user errors or input errors.
+
+  Exceptions of this type are handled by the command line tool
+  and result in clear error messages, as opposed to backtraces.
+  """
+  pass
diff --git a/yapf/yapflib/style.py b/yapf/yapflib/style.py
index 7d1ecbb..518ace8 100644
--- a/yapf/yapflib/style.py
+++ b/yapf/yapflib/style.py
@@ -18,13 +18,10 @@ import re
 import textwrap
 
 from yapf.yapflib import py3compat
+from yapf.yapflib import errors
 
 
-class Error(Exception):
-  pass
-
-
-class StyleConfigError(Error):
+class StyleConfigError(errors.YapfError):
   """Raised when there's a problem reading the style configuration."""
   pass
 


Test patch:
diff --git a/yapftests/main_test.py b/yapftests/main_test.py
index 314dcba..e216d67 100644
--- a/yapftests/main_test.py
+++ b/yapftests/main_test.py
@@ -57,10 +57,23 @@ def patched_input(code):
     yapf.py3compat.raw_input = raw_input
 
 
+class RunMainTest(unittest.TestCase):
+
+  def testShouldHandleYapfError(self):
+    """run_main should handle YapfError and sys.exit(1)"""
+    expected_message = 'yapf: Input filenames did not match any python files\n'
+    sys.argv = ['yapf', 'foo.c']
+    with captured_output() as (out, err):
+      with self.assertRaises(SystemExit):
+        ret = yapf.run_main()
+      self.assertEqual(out.getvalue(), '')
+      self.assertEqual(err.getvalue(), expected_message)
+
+
 class MainTest(unittest.TestCase):
 
   def testNoPythonFilesMatched(self):
-    with self.assertRaisesRegexp(yapf.YapfError,
+    with self.assertRaisesRegexp(yapf.errors.YapfError,
                                  'did not match any python files'):
       yapf.main(['yapf', 'foo.c'])