Skip to content

Read options from config #1668

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Sep 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,28 @@ Want to know if a word you're proposing exists in codespell already? It is possi
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
-----------------

Expand Down
28 changes: 28 additions & 0 deletions codespell_lib/_codespell.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import argparse
import codecs
import configparser
import fnmatch
import os
import re
Expand Down Expand Up @@ -361,12 +362,39 @@ def parse_options(args):
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than re-calling this, would it be better to do:

if config.has_section(...):
    ...
    options = parser.parse_args(cfg_args)
else:
    options = argparse.NameSpace()

options = parser.parse_args(list(args), namespace=options)

If it's equivalent, it seems simpler/cleaner

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only re-parses if config.has_section(). The args have to be parsed first (line 372) before checking the config, to see if there is a --config option passed. And they have to be re-parsed at the end in order to override the options from the config file (which was the requested behavior). Following your suggestion, you would be re-parsing it twice if there were no config files, which seems pointless.

It's your project though, feel free to re-implement it yourself if you have a better idea.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh right, forgot about the need to parse to see the --config value. This should be fine then!


if not options.files:
options.files.append('.')

Expand Down
33 changes: 33 additions & 0 deletions codespell_lib/tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,39 @@ def test_ignore_regex_flag(tmpdir, capsys):
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':
Expand Down