Read options from config (#1668)

* Read options from config

* Fix assert in test

* Fix tests

* Fix readme to pass rst checks in Travis

* Also support .codespellrc config file

* Fix flake8 error

* CLI args override config args

* Rename tool:codespell to just codespell in config

* Fix typo in readme

* Remove unnecessary check for existance of config files (configparser already handles this case)
diff --git a/README.rst b/README.rst
index d99331e..9058118 100644
--- a/README.rst
+++ b/README.rst
@@ -78,6 +78,28 @@
     echo "word" | codespell -
     echo "1stword,2ndword" | codespell -
 
+Using a config file
+-------------------
+
+Command line options can also be specified in a config file.
+
+When running ``codespell``, it will check in the current directory for a file
+named ``setup.cfg`` or ``.codespellrc`` (or a file specified via ``--config``),
+containing an entry named ``[codespell]``. Each command line argument can
+be specified in this file (without the preceding dashes), for example::
+
+    [codespell]
+    skip = *.po,*.ts,./src/3rdParty,./src/Test
+    count =
+    quiet-level = 3
+
+This is equivalent to running::
+
+    codespell --quiet-level 3 --count --skip "*.po,*.ts,./src/3rdParty,./src/Test"
+
+Any options specified in the command line will *override* options from the
+config file.
+
 Dictionary format
 -----------------
 
diff --git a/codespell_lib/_codespell.py b/codespell_lib/_codespell.py
index 83ebaa4..7ccff56 100755
--- a/codespell_lib/_codespell.py
+++ b/codespell_lib/_codespell.py
@@ -21,6 +21,7 @@
 
 import argparse
 import codecs
+import configparser
 import fnmatch
 import os
 import re
@@ -361,12 +362,39 @@
                         help='print LINES of leading context')
     parser.add_argument('-C', '--context', type=int, metavar='LINES',
                         help='print LINES of surrounding context')
+    parser.add_argument('--config', type=str,
+                        help='path to config file.')
 
     parser.add_argument('files', nargs='*',
                         help='files or directories to check')
 
+    # Parse command line options.
     options = parser.parse_args(list(args))
 
+    # Load config files and look for ``codespell`` options.
+    cfg_files = ['setup.cfg', '.codespellrc']
+    if options.config:
+        cfg_files.append(options.config)
+    config = configparser.ConfigParser()
+    config.read(cfg_files)
+
+    if config.has_section('codespell'):
+        # Build a "fake" argv list using option name and value.
+        cfg_args = []
+        for key in config['codespell']:
+            # Add option as arg.
+            cfg_args.append("--%s" % key)
+            # If value is blank, skip.
+            val = config['codespell'][key]
+            if val != "":
+                cfg_args.append(val)
+
+        # Parse config file options.
+        options = parser.parse_args(cfg_args)
+
+        # Re-parse command line options to override config.
+        options = parser.parse_args(list(args), namespace=options)
+
     if not options.files:
         options.files.append('.')
 
diff --git a/codespell_lib/tests/test_basic.py b/codespell_lib/tests/test_basic.py
index 876e24c..2e8fd73 100644
--- a/codespell_lib/tests/test_basic.py
+++ b/codespell_lib/tests/test_basic.py
@@ -488,6 +488,39 @@
     assert cs.main(f.name, r'--ignore-regex=\Wdonn\W') == 1
 
 
+def test_config(tmpdir, capsys):
+    """
+    Tests loading options from a config file.
+    """
+    d = str(tmpdir)
+
+    # Create sample files.
+    with open(op.join(d, 'bad.txt'), 'w') as f:
+        f.write('abandonned donn\n')
+    with open(op.join(d, 'good.txt'), 'w') as f:
+        f.write("good")
+
+    # Create a config file.
+    conffile = op.join(d, 'config.cfg')
+    with open(conffile, 'w') as f:
+        f.write(
+            '[codespell]\n'
+            'skip = bad.txt\n'
+            'count = \n'
+        )
+
+    # Should fail when checking both.
+    code, stdout, _ = cs.main(d, count=True, std=True)
+    # Code in this case is not exit code, but count of misspellings.
+    assert code == 2
+    assert 'bad.txt' in stdout
+
+    # Should pass when skipping bad.txt
+    code, stdout, _ = cs.main('--config', conffile, d, count=True, std=True)
+    assert code == 0
+    assert 'bad.txt' not in stdout
+
+
 @contextlib.contextmanager
 def FakeStdin(text):
     if sys.version[0] == '2':